networkgraph.src.js 122 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901
  1. /**
  2. * @license Highcharts JS v9.1.1 (2021-06-04)
  3. *
  4. * Force directed graph module
  5. *
  6. * (c) 2010-2021 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/Series/Series.js'], _modules['Core/Utilities.js']], function (H, Point, Series, 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. 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. 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 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-2021 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-2021 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/Animation/AnimationUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (Chart, A, H, U) {
  960. /* *
  961. *
  962. * Networkgraph series
  963. *
  964. * (c) 2010-2021 Paweł Fus
  965. *
  966. * License: www.highcharts.com/license
  967. *
  968. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  969. *
  970. * */
  971. var setAnimation = A.setAnimation;
  972. var addEvent = U.addEvent,
  973. clamp = U.clamp,
  974. defined = U.defined,
  975. extend = U.extend,
  976. isFunction = U.isFunction,
  977. pick = U.pick;
  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. let 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-2021 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 = void 0,
  1586. newPlotY = void 0,
  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) {
  1613. if (point.hasDragged) {
  1614. if (this.layout.enableSimulation) {
  1615. this.layout.start();
  1616. }
  1617. else {
  1618. this.chart.redraw();
  1619. }
  1620. }
  1621. point.inDragMode = point.hasDragged = false;
  1622. if (!this.options.fixedDraggable) {
  1623. delete point.fixedPosition;
  1624. }
  1625. }
  1626. },
  1627. // Draggable mode:
  1628. /**
  1629. * Redraw halo on mousemove during the drag&drop action.
  1630. *
  1631. * @private
  1632. * @param {Highcharts.Point} point The point that should show halo.
  1633. * @return {void}
  1634. */
  1635. redrawHalo: function (point) {
  1636. if (point && this.halo) {
  1637. this.halo.attr({
  1638. d: point.haloPath(this.options.states.hover.halo.size)
  1639. });
  1640. }
  1641. }
  1642. };
  1643. /*
  1644. * Draggable mode:
  1645. */
  1646. addEvent(Chart, 'load', function () {
  1647. var chart = this,
  1648. mousedownUnbinder,
  1649. mousemoveUnbinder,
  1650. mouseupUnbinder;
  1651. if (chart.container) {
  1652. mousedownUnbinder = addEvent(chart.container, 'mousedown', function (event) {
  1653. var point = chart.hoverPoint;
  1654. if (point &&
  1655. point.series &&
  1656. point.series.hasDraggableNodes &&
  1657. point.series.options.draggable) {
  1658. point.series.onMouseDown(point, event);
  1659. mousemoveUnbinder = addEvent(chart.container, 'mousemove', function (e) {
  1660. return point &&
  1661. point.series &&
  1662. point.series.onMouseMove(point, e);
  1663. });
  1664. mouseupUnbinder = addEvent(chart.container.ownerDocument, 'mouseup', function (e) {
  1665. mousemoveUnbinder();
  1666. mouseupUnbinder();
  1667. return point &&
  1668. point.series &&
  1669. point.series.onMouseUp(point, e);
  1670. });
  1671. }
  1672. });
  1673. }
  1674. addEvent(chart, 'destroy', function () {
  1675. mousedownUnbinder();
  1676. });
  1677. });
  1678. });
  1679. _registerModule(_modules, 'Series/Networkgraph/Networkgraph.js', [_modules['Core/Globals.js'], _modules['Mixins/Nodes.js'], _modules['Core/Series/Point.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (H, NodesMixin, Point, Series, SeriesRegistry, U) {
  1680. /* *
  1681. *
  1682. * Networkgraph series
  1683. *
  1684. * (c) 2010-2021 Paweł Fus
  1685. *
  1686. * License: www.highcharts.com/license
  1687. *
  1688. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  1689. *
  1690. * */
  1691. var __extends = (this && this.__extends) || (function () {
  1692. var extendStatics = function (d,
  1693. b) {
  1694. extendStatics = Object.setPrototypeOf ||
  1695. ({ __proto__: [] } instanceof Array && function (d,
  1696. b) { d.__proto__ = b; }) ||
  1697. function (d,
  1698. b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  1699. return extendStatics(d, b);
  1700. };
  1701. return function (d, b) {
  1702. extendStatics(d, b);
  1703. function __() { this.constructor = d; }
  1704. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  1705. };
  1706. })();
  1707. var seriesTypes = SeriesRegistry.seriesTypes;
  1708. var addEvent = U.addEvent,
  1709. css = U.css,
  1710. defined = U.defined,
  1711. extend = U.extend,
  1712. merge = U.merge,
  1713. pick = U.pick;
  1714. var dragNodesMixin = H.dragNodesMixin;
  1715. /**
  1716. * Formatter callback function.
  1717. *
  1718. * @callback Highcharts.SeriesNetworkgraphDataLabelsFormatterCallbackFunction
  1719. *
  1720. * @param {Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject|Highcharts.PointLabelObject} this
  1721. * Data label context to format
  1722. *
  1723. * @return {string}
  1724. * Formatted data label text
  1725. */
  1726. /**
  1727. * Context for the formatter function.
  1728. *
  1729. * @interface Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject
  1730. * @extends Highcharts.PointLabelObject
  1731. * @since 7.0.0
  1732. */ /**
  1733. * The color of the node.
  1734. * @name Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject#color
  1735. * @type {Highcharts.ColorString}
  1736. * @since 7.0.0
  1737. */ /**
  1738. * The point (node) object. The node name, if defined, is available through
  1739. * `this.point.name`. Arrays: `this.point.linksFrom` and `this.point.linksTo`
  1740. * contains all nodes connected to this point.
  1741. * @name Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject#point
  1742. * @type {Highcharts.Point}
  1743. * @since 7.0.0
  1744. */ /**
  1745. * The ID of the node.
  1746. * @name Highcharts.SeriesNetworkgraphDataLabelsFormatterContextObject#key
  1747. * @type {string}
  1748. * @since 7.0.0
  1749. */
  1750. ''; // detach doclets above
  1751. /* *
  1752. *
  1753. * Class
  1754. *
  1755. * */
  1756. /**
  1757. * @private
  1758. * @class
  1759. * @name Highcharts.seriesTypes.networkgraph
  1760. *
  1761. * @extends Highcharts.Series
  1762. */
  1763. var NetworkgraphSeries = /** @class */ (function (_super) {
  1764. __extends(NetworkgraphSeries, _super);
  1765. function NetworkgraphSeries() {
  1766. /* *
  1767. *
  1768. * Static Properties
  1769. *
  1770. * */
  1771. var _this = _super !== null && _super.apply(this,
  1772. arguments) || this;
  1773. /* *
  1774. *
  1775. * Properties
  1776. *
  1777. * */
  1778. _this.data = void 0;
  1779. _this.nodes = void 0;
  1780. _this.options = void 0;
  1781. _this.points = void 0;
  1782. return _this;
  1783. }
  1784. /**
  1785. * A networkgraph is a type of relationship chart, where connnections
  1786. * (links) attracts nodes (points) and other nodes repulse each other.
  1787. *
  1788. * @extends plotOptions.line
  1789. * @product highcharts
  1790. * @sample highcharts/demo/network-graph/
  1791. * Networkgraph
  1792. * @since 7.0.0
  1793. * @excluding boostThreshold, animation, animationLimit, connectEnds,
  1794. * colorAxis, colorKey, connectNulls, cropThreshold, dragDrop,
  1795. * getExtremesFromAll, label, linecap, negativeColor,
  1796. * pointInterval, pointIntervalUnit, pointPlacement,
  1797. * pointStart, softThreshold, stack, stacking, step,
  1798. * threshold, xAxis, yAxis, zoneAxis, dataSorting,
  1799. * boostBlending
  1800. * @requires modules/networkgraph
  1801. * @optionparent plotOptions.networkgraph
  1802. */
  1803. NetworkgraphSeries.defaultOptions = merge(Series.defaultOptions, {
  1804. stickyTracking: false,
  1805. /**
  1806. * @ignore-option
  1807. * @private
  1808. */
  1809. inactiveOtherPoints: true,
  1810. marker: {
  1811. enabled: true,
  1812. states: {
  1813. /**
  1814. * The opposite state of a hover for a single point node.
  1815. * Applied to all not connected nodes to the hovered one.
  1816. *
  1817. * @declare Highcharts.PointStatesInactiveOptionsObject
  1818. */
  1819. inactive: {
  1820. /**
  1821. * Opacity of inactive markers.
  1822. */
  1823. opacity: 0.3,
  1824. /**
  1825. * Animation when not hovering over the node.
  1826. *
  1827. * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
  1828. */
  1829. animation: {
  1830. /** @internal */
  1831. duration: 50
  1832. }
  1833. }
  1834. }
  1835. },
  1836. states: {
  1837. /**
  1838. * The opposite state of a hover for a single point link. Applied
  1839. * to all links that are not comming from the hovered node.
  1840. *
  1841. * @declare Highcharts.SeriesStatesInactiveOptionsObject
  1842. */
  1843. inactive: {
  1844. /**
  1845. * Opacity of inactive links.
  1846. */
  1847. linkOpacity: 0.3,
  1848. /**
  1849. * Animation when not hovering over the node.
  1850. *
  1851. * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
  1852. */
  1853. animation: {
  1854. /** @internal */
  1855. duration: 50
  1856. }
  1857. }
  1858. },
  1859. /**
  1860. * @sample highcharts/series-networkgraph/link-datalabels
  1861. * Networkgraph with labels on links
  1862. * @sample highcharts/series-networkgraph/textpath-datalabels
  1863. * Networkgraph with labels around nodes
  1864. * @sample highcharts/series-networkgraph/link-datalabels
  1865. * Data labels moved into the nodes
  1866. * @sample highcharts/series-networkgraph/link-datalabels
  1867. * Data labels moved under the links
  1868. *
  1869. * @declare Highcharts.SeriesNetworkgraphDataLabelsOptionsObject
  1870. *
  1871. * @private
  1872. */
  1873. dataLabels: {
  1874. /**
  1875. * The
  1876. * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  1877. * specifying what to show for _node_ in the networkgraph. In v7.0
  1878. * defaults to `{key}`, since v7.1 defaults to `undefined` and
  1879. * `formatter` is used instead.
  1880. *
  1881. * @type {string}
  1882. * @since 7.0.0
  1883. * @apioption plotOptions.networkgraph.dataLabels.format
  1884. */
  1885. // eslint-disable-next-line valid-jsdoc
  1886. /**
  1887. * Callback JavaScript function to format the data label for a node.
  1888. * Note that if a `format` is defined, the format takes precedence
  1889. * and the formatter is ignored.
  1890. *
  1891. * @type {Highcharts.SeriesNetworkgraphDataLabelsFormatterCallbackFunction}
  1892. * @since 7.0.0
  1893. */
  1894. formatter: function () {
  1895. return this.key;
  1896. },
  1897. /**
  1898. * The
  1899. * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  1900. * specifying what to show for _links_ in the networkgraph.
  1901. * (Default: `undefined`)
  1902. *
  1903. * @type {string}
  1904. * @since 7.1.0
  1905. * @apioption plotOptions.networkgraph.dataLabels.linkFormat
  1906. */
  1907. // eslint-disable-next-line valid-jsdoc
  1908. /**
  1909. * Callback to format data labels for _links_ in the sankey diagram.
  1910. * The `linkFormat` option takes precedence over the
  1911. * `linkFormatter`.
  1912. *
  1913. * @type {Highcharts.SeriesNetworkgraphDataLabelsFormatterCallbackFunction}
  1914. * @since 7.1.0
  1915. */
  1916. linkFormatter: function () {
  1917. return (this.point.fromNode.name +
  1918. '<br>' +
  1919. this.point.toNode.name);
  1920. },
  1921. /**
  1922. * Options for a _link_ label text which should follow link
  1923. * connection. Border and background are disabled for a label that
  1924. * follows a path.
  1925. *
  1926. * **Note:** Only SVG-based renderer supports this option. Setting
  1927. * `useHTML` to true will disable this option.
  1928. *
  1929. * @extends plotOptions.networkgraph.dataLabels.textPath
  1930. * @since 7.1.0
  1931. */
  1932. linkTextPath: {
  1933. enabled: true
  1934. },
  1935. textPath: {
  1936. enabled: false
  1937. },
  1938. style: {
  1939. transition: 'opacity 2000ms'
  1940. }
  1941. },
  1942. /**
  1943. * Link style options
  1944. * @private
  1945. */
  1946. link: {
  1947. /**
  1948. * A name for the dash style to use for links.
  1949. *
  1950. * @type {string}
  1951. * @apioption plotOptions.networkgraph.link.dashStyle
  1952. */
  1953. /**
  1954. * Color of the link between two nodes.
  1955. */
  1956. color: 'rgba(100, 100, 100, 0.5)',
  1957. /**
  1958. * Width (px) of the link between two nodes.
  1959. */
  1960. width: 1
  1961. },
  1962. /**
  1963. * Flag to determine if nodes are draggable or not.
  1964. * @private
  1965. */
  1966. draggable: true,
  1967. layoutAlgorithm: {
  1968. /**
  1969. * Repulsive force applied on a node. Passed are two arguments:
  1970. * - `d` - which is current distance between two nodes
  1971. * - `k` - which is desired distance between two nodes
  1972. *
  1973. * In `verlet` integration, defaults to:
  1974. * `function (d, k) { return (k - d) / d * (k > d ? 1 : 0) }`
  1975. *
  1976. * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
  1977. *
  1978. * @sample highcharts/series-networkgraph/forces/
  1979. * Custom forces with Euler integration
  1980. * @sample highcharts/series-networkgraph/cuboids/
  1981. * Custom forces with Verlet integration
  1982. *
  1983. * @type {Function}
  1984. * @default function (d, k) { return k * k / d; }
  1985. * @apioption plotOptions.networkgraph.layoutAlgorithm.repulsiveForce
  1986. */
  1987. /**
  1988. * Attraction force applied on a node which is conected to another
  1989. * node by a link. Passed are two arguments:
  1990. * - `d` - which is current distance between two nodes
  1991. * - `k` - which is desired distance between two nodes
  1992. *
  1993. * In `verlet` integration, defaults to:
  1994. * `function (d, k) { return (k - d) / d; }`
  1995. *
  1996. * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
  1997. *
  1998. * @sample highcharts/series-networkgraph/forces/
  1999. * Custom forces with Euler integration
  2000. * @sample highcharts/series-networkgraph/cuboids/
  2001. * Custom forces with Verlet integration
  2002. *
  2003. * @type {Function}
  2004. * @default function (d, k) { return k * k / d; }
  2005. * @apioption plotOptions.networkgraph.layoutAlgorithm.attractiveForce
  2006. */
  2007. /**
  2008. * Ideal length (px) of the link between two nodes. When not
  2009. * defined, length is calculated as:
  2010. * `Math.pow(availableWidth * availableHeight / nodesLength, 0.4);`
  2011. *
  2012. * Note: Because of the algorithm specification, length of each link
  2013. * might be not exactly as specified.
  2014. *
  2015. * @sample highcharts/series-networkgraph/styled-links/
  2016. * Numerical values
  2017. *
  2018. * @type {number}
  2019. * @apioption plotOptions.networkgraph.layoutAlgorithm.linkLength
  2020. */
  2021. /**
  2022. * Initial layout algorithm for positioning nodes. Can be one of
  2023. * built-in options ("circle", "random") or a function where
  2024. * positions should be set on each node (`this.nodes`) as
  2025. * `node.plotX` and `node.plotY`
  2026. *
  2027. * @sample highcharts/series-networkgraph/initial-positions/
  2028. * Initial positions with callback
  2029. *
  2030. * @type {"circle"|"random"|Function}
  2031. */
  2032. initialPositions: 'circle',
  2033. /**
  2034. * When `initialPositions` are set to 'circle',
  2035. * `initialPositionRadius` is a distance from the center of circle,
  2036. * in which nodes are created.
  2037. *
  2038. * @type {number}
  2039. * @default 1
  2040. * @since 7.1.0
  2041. */
  2042. initialPositionRadius: 1,
  2043. /**
  2044. * Experimental. Enables live simulation of the algorithm
  2045. * implementation. All nodes are animated as the forces applies on
  2046. * them.
  2047. *
  2048. * @sample highcharts/demo/network-graph/
  2049. * Live simulation enabled
  2050. */
  2051. enableSimulation: false,
  2052. /**
  2053. * Barnes-Hut approximation only.
  2054. * Deteremines when distance between cell and node is small enough
  2055. * to caculate forces. Value of `theta` is compared directly with
  2056. * quotient `s / d`, where `s` is the size of the cell, and `d` is
  2057. * distance between center of cell's mass and currently compared
  2058. * node.
  2059. *
  2060. * @see [layoutAlgorithm.approximation](#series.networkgraph.layoutAlgorithm.approximation)
  2061. *
  2062. * @since 7.1.0
  2063. */
  2064. theta: 0.5,
  2065. /**
  2066. * Verlet integration only.
  2067. * Max speed that node can get in one iteration. In terms of
  2068. * simulation, it's a maximum translation (in pixels) that node can
  2069. * move (in both, x and y, dimensions). While `friction` is applied
  2070. * on all nodes, max speed is applied only for nodes that move very
  2071. * fast, for example small or disconnected ones.
  2072. *
  2073. * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
  2074. * @see [layoutAlgorithm.friction](#series.networkgraph.layoutAlgorithm.friction)
  2075. *
  2076. * @since 7.1.0
  2077. */
  2078. maxSpeed: 10,
  2079. /**
  2080. * Approximation used to calculate repulsive forces affecting nodes.
  2081. * By default, when calculateing net force, nodes are compared
  2082. * against each other, which gives O(N^2) complexity. Using
  2083. * Barnes-Hut approximation, we decrease this to O(N log N), but the
  2084. * resulting graph will have different layout. Barnes-Hut
  2085. * approximation divides space into rectangles via quad tree, where
  2086. * forces exerted on nodes are calculated directly for nearby cells,
  2087. * and for all others, cells are treated as a separate node with
  2088. * center of mass.
  2089. *
  2090. * @see [layoutAlgorithm.theta](#series.networkgraph.layoutAlgorithm.theta)
  2091. *
  2092. * @sample highcharts/series-networkgraph/barnes-hut-approximation/
  2093. * A graph with Barnes-Hut approximation
  2094. *
  2095. * @type {string}
  2096. * @validvalue ["barnes-hut", "none"]
  2097. * @since 7.1.0
  2098. */
  2099. approximation: 'none',
  2100. /**
  2101. * Type of the algorithm used when positioning nodes.
  2102. *
  2103. * @type {string}
  2104. * @validvalue ["reingold-fruchterman"]
  2105. */
  2106. type: 'reingold-fruchterman',
  2107. /**
  2108. * Integration type. Available options are `'euler'` and `'verlet'`.
  2109. * Integration determines how forces are applied on particles. In
  2110. * Euler integration, force is applied direct as
  2111. * `newPosition += velocity;`.
  2112. * In Verlet integration, new position is based on a previous
  2113. * posittion without velocity:
  2114. * `newPosition += previousPosition - newPosition`.
  2115. *
  2116. * Note that different integrations give different results as forces
  2117. * are different.
  2118. *
  2119. * In Highcharts v7.0.x only `'euler'` integration was supported.
  2120. *
  2121. * @sample highcharts/series-networkgraph/integration-comparison/
  2122. * Comparison of Verlet and Euler integrations
  2123. *
  2124. * @type {string}
  2125. * @validvalue ["euler", "verlet"]
  2126. * @since 7.1.0
  2127. */
  2128. integration: 'euler',
  2129. /**
  2130. * Max number of iterations before algorithm will stop. In general,
  2131. * algorithm should find positions sooner, but when rendering huge
  2132. * number of nodes, it is recommended to increase this value as
  2133. * finding perfect graph positions can require more time.
  2134. */
  2135. maxIterations: 1000,
  2136. /**
  2137. * Gravitational const used in the barycenter force of the
  2138. * algorithm.
  2139. *
  2140. * @sample highcharts/series-networkgraph/forces/
  2141. * Custom forces with Euler integration
  2142. */
  2143. gravitationalConstant: 0.0625,
  2144. /**
  2145. * Friction applied on forces to prevent nodes rushing to fast to
  2146. * the desired positions.
  2147. */
  2148. friction: -0.981
  2149. },
  2150. showInLegend: false
  2151. });
  2152. return NetworkgraphSeries;
  2153. }(Series));
  2154. extend(NetworkgraphSeries.prototype, {
  2155. /**
  2156. * Array of internal forces. Each force should be later defined in
  2157. * integrations.js.
  2158. * @private
  2159. */
  2160. forces: ['barycenter', 'repulsive', 'attractive'],
  2161. hasDraggableNodes: true,
  2162. drawGraph: void 0,
  2163. isCartesian: false,
  2164. requireSorting: false,
  2165. directTouch: true,
  2166. noSharedTooltip: true,
  2167. pointArrayMap: ['from', 'to'],
  2168. trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
  2169. drawTracker: seriesTypes.column.prototype.drawTracker,
  2170. // Animation is run in `series.simulation`.
  2171. animate: void 0,
  2172. buildKDTree: H.noop,
  2173. /**
  2174. * Create a single node that holds information on incoming and outgoing
  2175. * links.
  2176. * @private
  2177. */
  2178. createNode: NodesMixin.createNode,
  2179. destroy: function () {
  2180. if (this.layout) {
  2181. this.layout.removeElementFromCollection(this, this.layout.series);
  2182. }
  2183. NodesMixin.destroy.call(this);
  2184. },
  2185. /* eslint-disable no-invalid-this, valid-jsdoc */
  2186. /**
  2187. * Extend init with base event, which should stop simulation during
  2188. * update. After data is updated, `chart.render` resumes the simulation.
  2189. * @private
  2190. */
  2191. init: function () {
  2192. var _this = this;
  2193. Series.prototype.init.apply(this, arguments);
  2194. addEvent(this, 'updatedData', function () {
  2195. if (_this.layout) {
  2196. _this.layout.stop();
  2197. }
  2198. });
  2199. addEvent(this, 'afterUpdate', function () {
  2200. _this.nodes.forEach(function (node) {
  2201. if (node && node.series) {
  2202. node.resolveColor();
  2203. }
  2204. });
  2205. });
  2206. return this;
  2207. },
  2208. /**
  2209. * Extend generatePoints by adding the nodes, which are Point objects
  2210. * but pushed to the this.nodes array.
  2211. * @private
  2212. */
  2213. generatePoints: function () {
  2214. var node,
  2215. i;
  2216. NodesMixin.generatePoints.apply(this, arguments);
  2217. // In networkgraph, it's fine to define stanalone nodes, create
  2218. // them:
  2219. if (this.options.nodes) {
  2220. this.options.nodes.forEach(function (nodeOptions) {
  2221. if (!this.nodeLookup[nodeOptions.id]) {
  2222. this.nodeLookup[nodeOptions.id] =
  2223. this.createNode(nodeOptions.id);
  2224. }
  2225. }, this);
  2226. }
  2227. for (i = this.nodes.length - 1; i >= 0; i--) {
  2228. node = this.nodes[i];
  2229. node.degree = node.getDegree();
  2230. node.radius = pick(node.marker && node.marker.radius, this.options.marker && this.options.marker.radius, 0);
  2231. // If node exists, but it's not available in nodeLookup,
  2232. // then it's leftover from previous runs (e.g. setData)
  2233. if (!this.nodeLookup[node.id]) {
  2234. node.remove();
  2235. }
  2236. }
  2237. this.data.forEach(function (link) {
  2238. link.formatPrefix = 'link';
  2239. });
  2240. this.indexateNodes();
  2241. },
  2242. /**
  2243. * In networkgraph, series.points refers to links,
  2244. * but series.nodes refers to actual points.
  2245. * @private
  2246. */
  2247. getPointsCollection: function () {
  2248. return this.nodes || [];
  2249. },
  2250. /**
  2251. * Set index for each node. Required for proper `node.update()`.
  2252. * Note that links are indexated out of the box in `generatePoints()`.
  2253. *
  2254. * @private
  2255. */
  2256. indexateNodes: function () {
  2257. this.nodes.forEach(function (node, index) {
  2258. node.index = index;
  2259. });
  2260. },
  2261. /**
  2262. * Extend the default marker attribs by using a non-rounded X position,
  2263. * otherwise the nodes will jump from pixel to pixel which looks a bit
  2264. * jaggy when approaching equilibrium.
  2265. * @private
  2266. */
  2267. markerAttribs: function (point, state) {
  2268. var attribs = Series.prototype.markerAttribs.call(this,
  2269. point,
  2270. state);
  2271. // series.render() is called before initial positions are set:
  2272. if (!defined(point.plotY)) {
  2273. attribs.y = 0;
  2274. }
  2275. attribs.x = (point.plotX || 0) - (attribs.width || 0) / 2;
  2276. return attribs;
  2277. },
  2278. /**
  2279. * Run pre-translation and register nodes&links to the deffered layout.
  2280. * @private
  2281. */
  2282. translate: function () {
  2283. if (!this.processedXData) {
  2284. this.processData();
  2285. }
  2286. this.generatePoints();
  2287. this.deferLayout();
  2288. this.nodes.forEach(function (node) {
  2289. // Draw the links from this node
  2290. node.isInside = true;
  2291. node.linksFrom.forEach(function (point) {
  2292. point.shapeType = 'path';
  2293. // Pass test in drawPoints
  2294. point.y = 1;
  2295. });
  2296. });
  2297. },
  2298. /**
  2299. * Defer the layout.
  2300. * Each series first registers all nodes and links, then layout
  2301. * calculates all nodes positions and calls `series.render()` in every
  2302. * simulation step.
  2303. *
  2304. * Note:
  2305. * Animation is done through `requestAnimationFrame` directly, without
  2306. * `Highcharts.animate()` use.
  2307. * @private
  2308. */
  2309. deferLayout: function () {
  2310. var layoutOptions = this.options.layoutAlgorithm,
  2311. graphLayoutsStorage = this.chart.graphLayoutsStorage,
  2312. graphLayoutsLookup = this.chart.graphLayoutsLookup,
  2313. chartOptions = this.chart.options.chart,
  2314. layout;
  2315. if (!this.visible) {
  2316. return;
  2317. }
  2318. if (!graphLayoutsStorage) {
  2319. this.chart.graphLayoutsStorage = graphLayoutsStorage = {};
  2320. this.chart.graphLayoutsLookup = graphLayoutsLookup = [];
  2321. }
  2322. layout = graphLayoutsStorage[layoutOptions.type];
  2323. if (!layout) {
  2324. layoutOptions.enableSimulation =
  2325. !defined(chartOptions.forExport) ?
  2326. layoutOptions.enableSimulation :
  2327. !chartOptions.forExport;
  2328. graphLayoutsStorage[layoutOptions.type] = layout =
  2329. new H.layouts[layoutOptions.type]();
  2330. layout.init(layoutOptions);
  2331. graphLayoutsLookup.splice(layout.index, 0, layout);
  2332. }
  2333. this.layout = layout;
  2334. layout.setArea(0, 0, this.chart.plotWidth, this.chart.plotHeight);
  2335. layout.addElementsToCollection([this], layout.series);
  2336. layout.addElementsToCollection(this.nodes, layout.nodes);
  2337. layout.addElementsToCollection(this.points, layout.links);
  2338. },
  2339. /**
  2340. * Extend the render function to also render this.nodes together with
  2341. * the points.
  2342. * @private
  2343. */
  2344. render: function () {
  2345. var series = this,
  2346. points = series.points,
  2347. hoverPoint = series.chart.hoverPoint,
  2348. dataLabels = [];
  2349. // Render markers:
  2350. series.points = series.nodes;
  2351. seriesTypes.line.prototype.render.call(this);
  2352. series.points = points;
  2353. points.forEach(function (point) {
  2354. if (point.fromNode && point.toNode) {
  2355. point.renderLink();
  2356. point.redrawLink();
  2357. }
  2358. });
  2359. if (hoverPoint && hoverPoint.series === series) {
  2360. series.redrawHalo(hoverPoint);
  2361. }
  2362. if (series.chart.hasRendered &&
  2363. !series.options.dataLabels.allowOverlap) {
  2364. series.nodes.concat(series.points).forEach(function (node) {
  2365. if (node.dataLabel) {
  2366. dataLabels.push(node.dataLabel);
  2367. }
  2368. });
  2369. series.chart.hideOverlappingLabels(dataLabels);
  2370. }
  2371. },
  2372. // Networkgraph has two separate collecions of nodes and lines, render
  2373. // dataLabels for both sets:
  2374. drawDataLabels: function () {
  2375. var textPath = this.options.dataLabels.textPath;
  2376. // Render node labels:
  2377. Series.prototype.drawDataLabels.apply(this, arguments);
  2378. // Render link labels:
  2379. this.points = this.data;
  2380. this.options.dataLabels.textPath =
  2381. this.options.dataLabels.linkTextPath;
  2382. Series.prototype.drawDataLabels.apply(this, arguments);
  2383. // Restore nodes
  2384. this.points = this.nodes;
  2385. this.options.dataLabels.textPath = textPath;
  2386. },
  2387. // Return the presentational attributes.
  2388. pointAttribs: function (point, state) {
  2389. // By default, only `selected` state is passed on
  2390. var pointState = state || point && point.state || 'normal',
  2391. attribs = Series.prototype.pointAttribs.call(this,
  2392. point,
  2393. pointState),
  2394. stateOptions = this.options.states[pointState];
  2395. if (point && !point.isNode) {
  2396. attribs = point.getLinkAttributes();
  2397. // For link, get prefixed names:
  2398. if (stateOptions) {
  2399. attribs = {
  2400. // TO DO: API?
  2401. stroke: stateOptions.linkColor || attribs.stroke,
  2402. dashstyle: (stateOptions.linkDashStyle || attribs.dashstyle),
  2403. opacity: pick(stateOptions.linkOpacity, attribs.opacity),
  2404. 'stroke-width': stateOptions.linkColor ||
  2405. attribs['stroke-width']
  2406. };
  2407. }
  2408. }
  2409. return attribs;
  2410. },
  2411. // Draggable mode:
  2412. /**
  2413. * Redraw halo on mousemove during the drag&drop action.
  2414. * @private
  2415. * @param {Highcharts.Point} point The point that should show halo.
  2416. */
  2417. redrawHalo: dragNodesMixin.redrawHalo,
  2418. /**
  2419. * Mouse down action, initializing drag&drop mode.
  2420. * @private
  2421. * @param {global.Event} event Browser event, before normalization.
  2422. * @param {Highcharts.Point} point The point that event occured.
  2423. */
  2424. onMouseDown: dragNodesMixin.onMouseDown,
  2425. /**
  2426. * Mouse move action during drag&drop.
  2427. * @private
  2428. * @param {global.Event} event Browser event, before normalization.
  2429. * @param {Highcharts.Point} point The point that event occured.
  2430. */
  2431. onMouseMove: dragNodesMixin.onMouseMove,
  2432. /**
  2433. * Mouse up action, finalizing drag&drop.
  2434. * @private
  2435. * @param {Highcharts.Point} point The point that event occured.
  2436. */
  2437. onMouseUp: dragNodesMixin.onMouseUp,
  2438. /**
  2439. * When state should be passed down to all points, concat nodes and
  2440. * links and apply this state to all of them.
  2441. * @private
  2442. */
  2443. setState: function (state, inherit) {
  2444. if (inherit) {
  2445. this.points = this.nodes.concat(this.data);
  2446. Series.prototype.setState.apply(this, arguments);
  2447. this.points = this.data;
  2448. }
  2449. else {
  2450. Series.prototype.setState.apply(this, arguments);
  2451. }
  2452. // If simulation is done, re-render points with new states:
  2453. if (!this.layout.simulation && !state) {
  2454. this.render();
  2455. }
  2456. }
  2457. });
  2458. /* *
  2459. *
  2460. * Class
  2461. *
  2462. * */
  2463. var NetworkgraphPoint = /** @class */ (function (_super) {
  2464. __extends(NetworkgraphPoint, _super);
  2465. function NetworkgraphPoint() {
  2466. /* *
  2467. *
  2468. * Properties
  2469. *
  2470. * */
  2471. var _this = _super !== null && _super.apply(this,
  2472. arguments) || this;
  2473. _this.degree = void 0;
  2474. _this.linksFrom = void 0;
  2475. _this.linksTo = void 0;
  2476. _this.options = void 0;
  2477. _this.radius = void 0;
  2478. _this.series = void 0;
  2479. _this.toNode = void 0;
  2480. return _this;
  2481. }
  2482. return NetworkgraphPoint;
  2483. }(Series.prototype.pointClass));
  2484. extend(NetworkgraphPoint.prototype, {
  2485. setState: NodesMixin.setNodeState,
  2486. /**
  2487. * Basic `point.init()` and additional styles applied when
  2488. * `series.draggable` is enabled.
  2489. * @private
  2490. */
  2491. init: function () {
  2492. Point.prototype.init.apply(this, arguments);
  2493. if (this.series.options.draggable &&
  2494. !this.series.chart.styledMode) {
  2495. addEvent(this, 'mouseOver', function () {
  2496. css(this.series.chart.container, { cursor: 'move' });
  2497. });
  2498. addEvent(this, 'mouseOut', function () {
  2499. css(this.series.chart.container, { cursor: 'default' });
  2500. });
  2501. }
  2502. return this;
  2503. },
  2504. /**
  2505. * Return degree of a node. If node has no connections, it still has
  2506. * deg=1.
  2507. * @private
  2508. * @return {number}
  2509. */
  2510. getDegree: function () {
  2511. var deg = this.isNode ?
  2512. this.linksFrom.length + this.linksTo.length :
  2513. 0;
  2514. return deg === 0 ? 1 : deg;
  2515. },
  2516. // Links:
  2517. /**
  2518. * Get presentational attributes of link connecting two nodes.
  2519. * @private
  2520. * @return {Highcharts.SVGAttributes}
  2521. */
  2522. getLinkAttributes: function () {
  2523. var linkOptions = this.series.options.link,
  2524. pointOptions = this.options;
  2525. return {
  2526. 'stroke-width': pick(pointOptions.width, linkOptions.width),
  2527. stroke: (pointOptions.color || linkOptions.color),
  2528. dashstyle: (pointOptions.dashStyle || linkOptions.dashStyle),
  2529. opacity: pick(pointOptions.opacity, linkOptions.opacity, 1)
  2530. };
  2531. },
  2532. /**
  2533. * Render link and add it to the DOM.
  2534. * @private
  2535. */
  2536. renderLink: function () {
  2537. var attribs;
  2538. if (!this.graphic) {
  2539. this.graphic = this.series.chart.renderer
  2540. .path(this.getLinkPath())
  2541. .addClass(this.getClassName(), true)
  2542. .add(this.series.group);
  2543. if (!this.series.chart.styledMode) {
  2544. attribs = this.series.pointAttribs(this);
  2545. this.graphic.attr(attribs);
  2546. (this.dataLabels || []).forEach(function (label) {
  2547. if (label) {
  2548. label.attr({
  2549. opacity: attribs.opacity
  2550. });
  2551. }
  2552. });
  2553. }
  2554. }
  2555. },
  2556. /**
  2557. * Redraw link's path.
  2558. * @private
  2559. */
  2560. redrawLink: function () {
  2561. var path = this.getLinkPath(),
  2562. attribs;
  2563. if (this.graphic) {
  2564. this.shapeArgs = {
  2565. d: path
  2566. };
  2567. if (!this.series.chart.styledMode) {
  2568. attribs = this.series.pointAttribs(this);
  2569. this.graphic.attr(attribs);
  2570. (this.dataLabels || []).forEach(function (label) {
  2571. if (label) {
  2572. label.attr({
  2573. opacity: attribs.opacity
  2574. });
  2575. }
  2576. });
  2577. }
  2578. this.graphic.animate(this.shapeArgs);
  2579. // Required for dataLabels
  2580. var start = path[0];
  2581. var end = path[1];
  2582. if (start[0] === 'M' && end[0] === 'L') {
  2583. this.plotX = (start[1] + end[1]) / 2;
  2584. this.plotY = (start[2] + end[2]) / 2;
  2585. }
  2586. }
  2587. },
  2588. /**
  2589. * Get mass fraction applied on two nodes connected to each other. By
  2590. * default, when mass is equal to `1`, mass fraction for both nodes
  2591. * equal to 0.5.
  2592. * @private
  2593. * @return {Highcharts.Dictionary<number>}
  2594. * For example `{ fromNode: 0.5, toNode: 0.5 }`
  2595. */
  2596. getMass: function () {
  2597. var m1 = this.fromNode.mass,
  2598. m2 = this.toNode.mass,
  2599. sum = m1 + m2;
  2600. return {
  2601. fromNode: 1 - m1 / sum,
  2602. toNode: 1 - m2 / sum
  2603. };
  2604. },
  2605. /**
  2606. * Get link path connecting two nodes.
  2607. * @private
  2608. * @return {Array<Highcharts.SVGPathArray>}
  2609. * Path: `['M', x, y, 'L', x, y]`
  2610. */
  2611. getLinkPath: function () {
  2612. var left = this.fromNode,
  2613. right = this.toNode;
  2614. // Start always from left to the right node, to prevent rendering
  2615. // labels upside down
  2616. if (left.plotX > right.plotX) {
  2617. left = this.toNode;
  2618. right = this.fromNode;
  2619. }
  2620. return [
  2621. ['M', left.plotX || 0, left.plotY || 0],
  2622. ['L', right.plotX || 0, right.plotY || 0]
  2623. ];
  2624. /*
  2625. IDEA: different link shapes?
  2626. return [
  2627. 'M',
  2628. from.plotX,
  2629. from.plotY,
  2630. 'Q',
  2631. (to.plotX + from.plotX) / 2,
  2632. (to.plotY + from.plotY) / 2 + 15,
  2633. to.plotX,
  2634. to.plotY
  2635. ];*/
  2636. },
  2637. isValid: function () {
  2638. return !this.isNode || defined(this.id);
  2639. },
  2640. /**
  2641. * Common method for removing points and nodes in networkgraph. To
  2642. * remove `link`, use `series.data[index].remove()`. To remove `node`
  2643. * with all connections, use `series.nodes[index].remove()`.
  2644. * @private
  2645. * @param {boolean} [redraw=true]
  2646. * Whether to redraw the chart or wait for an explicit call. When
  2647. * doing more operations on the chart, for example running
  2648. * `point.remove()` in a loop, it is best practice to set
  2649. * `redraw` to false and call `chart.redraw()` after.
  2650. * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=false]
  2651. * Whether to apply animation, and optionally animation
  2652. * configuration.
  2653. * @return {void}
  2654. */
  2655. remove: function (redraw, animation) {
  2656. var point = this,
  2657. series = point.series,
  2658. nodesOptions = series.options.nodes || [],
  2659. index,
  2660. i = nodesOptions.length;
  2661. // For nodes, remove all connected links:
  2662. if (point.isNode) {
  2663. // Temporary disable series.points array, because
  2664. // Series.removePoint() modifies it
  2665. series.points = [];
  2666. // Remove link from all nodes collections:
  2667. []
  2668. .concat(point.linksFrom)
  2669. .concat(point.linksTo)
  2670. .forEach(function (linkFromTo) {
  2671. // Incoming links
  2672. index = linkFromTo.fromNode.linksFrom.indexOf(linkFromTo);
  2673. if (index > -1) {
  2674. linkFromTo.fromNode.linksFrom.splice(index, 1);
  2675. }
  2676. // Outcoming links
  2677. index = linkFromTo.toNode.linksTo.indexOf(linkFromTo);
  2678. if (index > -1) {
  2679. linkFromTo.toNode.linksTo.splice(index, 1);
  2680. }
  2681. // Remove link from data/points collections
  2682. Series.prototype.removePoint.call(series, series.data.indexOf(linkFromTo), false, false);
  2683. });
  2684. // Restore points array, after links are removed
  2685. series.points = series.data.slice();
  2686. // Proceed with removing node. It's similar to
  2687. // Series.removePoint() method, but doesn't modify other arrays
  2688. series.nodes.splice(series.nodes.indexOf(point), 1);
  2689. // Remove node options from config
  2690. while (i--) {
  2691. if (nodesOptions[i].id === point.options.id) {
  2692. series.options.nodes.splice(i, 1);
  2693. break;
  2694. }
  2695. }
  2696. if (point) {
  2697. point.destroy();
  2698. }
  2699. // Run redraw if requested
  2700. series.isDirty = true;
  2701. series.isDirtyData = true;
  2702. if (redraw) {
  2703. series.chart.redraw(redraw);
  2704. }
  2705. }
  2706. else {
  2707. series.removePoint(series.data.indexOf(point), redraw, animation);
  2708. }
  2709. },
  2710. /**
  2711. * Destroy point. If it's a node, remove all links coming out of this
  2712. * node. Then remove point from the layout.
  2713. * @private
  2714. * @return {void}
  2715. */
  2716. destroy: function () {
  2717. if (this.isNode) {
  2718. this.linksFrom.concat(this.linksTo).forEach(function (link) {
  2719. // Removing multiple nodes at the same time
  2720. // will try to remove link between nodes twice
  2721. if (link.destroyElements) {
  2722. link.destroyElements();
  2723. }
  2724. });
  2725. }
  2726. this.series.layout.removeElementFromCollection(this, this.series.layout[this.isNode ? 'nodes' : 'links']);
  2727. return Point.prototype.destroy.apply(this, arguments);
  2728. }
  2729. });
  2730. NetworkgraphSeries.prototype.pointClass = NetworkgraphPoint;
  2731. SeriesRegistry.registerSeriesType('networkgraph', NetworkgraphSeries);
  2732. /* *
  2733. *
  2734. * Default Export
  2735. *
  2736. * */
  2737. /* *
  2738. *
  2739. * API Options
  2740. *
  2741. * */
  2742. /**
  2743. * A `networkgraph` series. If the [type](#series.networkgraph.type) option is
  2744. * not specified, it is inherited from [chart.type](#chart.type).
  2745. *
  2746. * @extends series,plotOptions.networkgraph
  2747. * @excluding boostThreshold, animation, animationLimit, connectEnds,
  2748. * connectNulls, cropThreshold, dragDrop, getExtremesFromAll, label,
  2749. * linecap, negativeColor, pointInterval, pointIntervalUnit,
  2750. * pointPlacement, pointStart, softThreshold, stack, stacking,
  2751. * step, threshold, xAxis, yAxis, zoneAxis, dataSorting,
  2752. * boostBlending
  2753. * @product highcharts
  2754. * @requires modules/networkgraph
  2755. * @apioption series.networkgraph
  2756. */
  2757. /**
  2758. * An array of data points for the series. For the `networkgraph` series type,
  2759. * points can be given in the following way:
  2760. *
  2761. * An array of objects with named values. The following snippet shows only a
  2762. * few settings, see the complete options set below. If the total number of
  2763. * data points exceeds the series'
  2764. * [turboThreshold](#series.area.turboThreshold), this option is not available.
  2765. *
  2766. * ```js
  2767. * data: [{
  2768. * from: 'Category1',
  2769. * to: 'Category2'
  2770. * }, {
  2771. * from: 'Category1',
  2772. * to: 'Category3'
  2773. * }]
  2774. * ```
  2775. *
  2776. * @type {Array<Object|Array|Number>}
  2777. * @extends series.line.data
  2778. * @excluding drilldown,marker,x,y,draDrop
  2779. * @sample {highcharts} highcharts/chart/reflow-true/
  2780. * Numerical values
  2781. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  2782. * Arrays of numeric x and y
  2783. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  2784. * Arrays of datetime x and y
  2785. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  2786. * Arrays of point.name and y
  2787. * @sample {highcharts} highcharts/series/data-array-of-objects/
  2788. * Config objects
  2789. * @product highcharts
  2790. * @apioption series.networkgraph.data
  2791. */
  2792. /**
  2793. * @type {Highcharts.SeriesNetworkgraphDataLabelsOptionsObject|Array<Highcharts.SeriesNetworkgraphDataLabelsOptionsObject>}
  2794. * @product highcharts
  2795. * @apioption series.networkgraph.data.dataLabels
  2796. */
  2797. /**
  2798. * The node that the link runs from.
  2799. *
  2800. * @type {string}
  2801. * @product highcharts
  2802. * @apioption series.networkgraph.data.from
  2803. */
  2804. /**
  2805. * The node that the link runs to.
  2806. *
  2807. * @type {string}
  2808. * @product highcharts
  2809. * @apioption series.networkgraph.data.to
  2810. */
  2811. /**
  2812. * A collection of options for the individual nodes. The nodes in a
  2813. * networkgraph diagram are auto-generated instances of `Highcharts.Point`,
  2814. * but options can be applied here and linked by the `id`.
  2815. *
  2816. * @sample highcharts/series-networkgraph/data-options/
  2817. * Networkgraph diagram with node options
  2818. *
  2819. * @type {Array<*>}
  2820. * @product highcharts
  2821. * @apioption series.networkgraph.nodes
  2822. */
  2823. /**
  2824. * The id of the auto-generated node, refering to the `from` or `to` setting of
  2825. * the link.
  2826. *
  2827. * @type {string}
  2828. * @product highcharts
  2829. * @apioption series.networkgraph.nodes.id
  2830. */
  2831. /**
  2832. * The color of the auto generated node.
  2833. *
  2834. * @type {Highcharts.ColorString}
  2835. * @product highcharts
  2836. * @apioption series.networkgraph.nodes.color
  2837. */
  2838. /**
  2839. * The color index of the auto generated node, especially for use in styled
  2840. * mode.
  2841. *
  2842. * @type {number}
  2843. * @product highcharts
  2844. * @apioption series.networkgraph.nodes.colorIndex
  2845. */
  2846. /**
  2847. * The name to display for the node in data labels and tooltips. Use this when
  2848. * the name is different from the `id`. Where the id must be unique for each
  2849. * node, this is not necessary for the name.
  2850. *
  2851. * @sample highcharts/series-networkgraph/data-options/
  2852. * Networkgraph diagram with node options
  2853. *
  2854. * @type {string}
  2855. * @product highcharts
  2856. * @apioption series.networkgraph.nodes.name
  2857. */
  2858. /**
  2859. * Mass of the node. By default, each node has mass equal to it's marker radius
  2860. * . Mass is used to determine how two connected nodes should affect
  2861. * each other:
  2862. *
  2863. * Attractive force is multiplied by the ratio of two connected
  2864. * nodes; if a big node has weights twice as the small one, then the small one
  2865. * will move towards the big one twice faster than the big one to the small one
  2866. * .
  2867. *
  2868. * @sample highcharts/series-networkgraph/ragdoll/
  2869. * Mass determined by marker.radius
  2870. *
  2871. * @type {number}
  2872. * @product highcharts
  2873. * @apioption series.networkgraph.nodes.mass
  2874. */
  2875. /**
  2876. * Individual data label for each node. The options are the same as
  2877. * the ones for [series.networkgraph.dataLabels](#series.networkgraph.dataLabels).
  2878. *
  2879. * @type {Highcharts.SeriesNetworkgraphDataLabelsOptionsObject|Array<Highcharts.SeriesNetworkgraphDataLabelsOptionsObject>}
  2880. *
  2881. * @apioption series.networkgraph.nodes.dataLabels
  2882. */
  2883. ''; // adds doclets above to transpiled file
  2884. return NetworkgraphSeries;
  2885. });
  2886. _registerModule(_modules, 'masters/modules/networkgraph.src.js', [], function () {
  2887. });
  2888. }));