sankey.src.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. /* *
  2. *
  3. * Sankey diagram module
  4. *
  5. * (c) 2010-2020 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import H from '../parts/Globals.js';
  14. /**
  15. * A node in a sankey diagram.
  16. *
  17. * @interface Highcharts.SankeyNodeObject
  18. * @extends Highcharts.Point
  19. * @product highcharts
  20. */ /**
  21. * The color of the auto generated node.
  22. *
  23. * @name Highcharts.SankeyNodeObject#color
  24. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  25. */ /**
  26. * The color index of the auto generated node, especially for use in styled
  27. * mode.
  28. *
  29. * @name Highcharts.SankeyNodeObject#colorIndex
  30. * @type {number}
  31. */ /**
  32. * An optional column index of where to place the node. The default behaviour is
  33. * to place it next to the preceding node.
  34. *
  35. * @see {@link https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/sankey-node-column/|Highcharts-Demo:}
  36. * Specified node column
  37. *
  38. * @name Highcharts.SankeyNodeObject#column
  39. * @type {number}
  40. * @since 6.0.5
  41. */ /**
  42. * The id of the auto-generated node, refering to the `from` or `to` setting of
  43. * the link.
  44. *
  45. * @name Highcharts.SankeyNodeObject#id
  46. * @type {string}
  47. */ /**
  48. * The name to display for the node in data labels and tooltips. Use this when
  49. * the name is different from the `id`. Where the id must be unique for each
  50. * node, this is not necessary for the name.
  51. *
  52. * @see {@link https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/sankey/|Highcharts-Demo:}
  53. * Sankey diagram with node options
  54. *
  55. * @name Highcharts.SankeyNodeObject#name
  56. * @type {string}
  57. * @product highcharts
  58. */ /**
  59. * The vertical offset of a node in terms of weight. Positive values shift the
  60. * node downwards, negative shift it upwards.
  61. *
  62. * @see {@link https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/sankey-node-column/|Highcharts-Demo:}
  63. * Specified node offset
  64. *
  65. * @name Highcharts.SankeyNodeObject#offset
  66. * @type {number}
  67. * @default 0
  68. * @since 6.0.5
  69. */
  70. /**
  71. * Formatter callback function.
  72. *
  73. * @callback Highcharts.SeriesSankeyDataLabelsFormatterCallbackFunction
  74. *
  75. * @param {Highcharts.SeriesSankeyDataLabelsFormatterContextObject|Highcharts.PointLabelObject} this
  76. * Data label context to format
  77. *
  78. * @return {string|undefined}
  79. * Formatted data label text
  80. */
  81. /**
  82. * Context for the node formatter function.
  83. *
  84. * @interface Highcharts.SeriesSankeyDataLabelsFormatterContextObject
  85. * @extends Highcharts.PointLabelObject
  86. */ /**
  87. * The node object. The node name, if defined, is available through
  88. * `this.point.name`.
  89. * @name Highcharts.SeriesSankeyDataLabelsFormatterContextObject#point
  90. * @type {Highcharts.SankeyNodeObject}
  91. */
  92. import Color from '../parts/Color.js';
  93. import Point from '../parts/Point.js';
  94. import U from '../parts/Utilities.js';
  95. var defined = U.defined, find = U.find, isObject = U.isObject, merge = U.merge, pick = U.pick, relativeLength = U.relativeLength, seriesType = U.seriesType, stableSort = U.stableSort;
  96. import '../parts/Options.js';
  97. import '../mixins/nodes.js';
  98. import mixinTreeSeries from '../mixins/tree-series.js';
  99. var getLevelOptions = mixinTreeSeries.getLevelOptions;
  100. // eslint-disable-next-line valid-jsdoc
  101. /**
  102. * @private
  103. */
  104. var getDLOptions = function getDLOptions(params) {
  105. var optionsPoint = (isObject(params.optionsPoint) ?
  106. params.optionsPoint.dataLabels :
  107. {}), optionsLevel = (isObject(params.level) ?
  108. params.level.dataLabels :
  109. {}), options = merge({
  110. style: {}
  111. }, optionsLevel, optionsPoint);
  112. return options;
  113. };
  114. /**
  115. * @private
  116. * @class
  117. * @name Highcharts.seriesTypes.sankey
  118. *
  119. * @augments Highcharts.Series
  120. */
  121. seriesType('sankey', 'column',
  122. /**
  123. * A sankey diagram is a type of flow diagram, in which the width of the
  124. * link between two nodes is shown proportionally to the flow quantity.
  125. *
  126. * @sample highcharts/demo/sankey-diagram/
  127. * Sankey diagram
  128. * @sample highcharts/plotoptions/sankey-inverted/
  129. * Inverted sankey diagram
  130. * @sample highcharts/plotoptions/sankey-outgoing
  131. * Sankey diagram with outgoing links
  132. *
  133. * @extends plotOptions.column
  134. * @since 6.0.0
  135. * @product highcharts
  136. * @excluding animationLimit, boostThreshold, borderRadius,
  137. * crisp, cropThreshold, colorAxis, colorKey, depth, dragDrop,
  138. * edgeColor, edgeWidth, findNearestPointBy, grouping,
  139. * groupPadding, groupZPadding, maxPointWidth, negativeColor,
  140. * pointInterval, pointIntervalUnit, pointPadding,
  141. * pointPlacement, pointRange, pointStart, pointWidth,
  142. * shadow, softThreshold, stacking, threshold, zoneAxis,
  143. * zones, minPointLength, dataSorting
  144. * @requires modules/sankey
  145. * @optionparent plotOptions.sankey
  146. */
  147. {
  148. borderWidth: 0,
  149. colorByPoint: true,
  150. /**
  151. * Higher numbers makes the links in a sankey diagram or dependency
  152. * wheelrender more curved. A `curveFactor` of 0 makes the lines
  153. * straight.
  154. *
  155. * @private
  156. */
  157. curveFactor: 0.33,
  158. /**
  159. * Options for the data labels appearing on top of the nodes and links.
  160. * For sankey charts, data labels are visible for the nodes by default,
  161. * but hidden for links. This is controlled by modifying the
  162. * `nodeFormat`, and the `format` that applies to links and is an empty
  163. * string by default.
  164. *
  165. * @declare Highcharts.SeriesSankeyDataLabelsOptionsObject
  166. *
  167. * @private
  168. */
  169. dataLabels: {
  170. enabled: true,
  171. backgroundColor: 'none',
  172. crop: false,
  173. /**
  174. * The
  175. * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  176. * specifying what to show for _nodes_ in the sankey diagram. By
  177. * default the `nodeFormatter` returns `{point.name}`.
  178. *
  179. * @sample highcharts/plotoptions/sankey-link-datalabels/
  180. * Node and link data labels
  181. *
  182. * @type {string}
  183. */
  184. nodeFormat: void 0,
  185. // eslint-disable-next-line valid-jsdoc
  186. /**
  187. * Callback to format data labels for _nodes_ in the sankey diagram.
  188. * The `nodeFormat` option takes precedence over the
  189. * `nodeFormatter`.
  190. *
  191. * @type {Highcharts.SeriesSankeyDataLabelsFormatterCallbackFunction}
  192. * @since 6.0.2
  193. */
  194. nodeFormatter: function () {
  195. return this.point.name;
  196. },
  197. format: void 0,
  198. // eslint-disable-next-line valid-jsdoc
  199. /**
  200. * @type {Highcharts.SeriesSankeyDataLabelsFormatterCallbackFunction}
  201. */
  202. formatter: function () {
  203. return;
  204. },
  205. inside: true
  206. },
  207. /**
  208. * @ignore-option
  209. *
  210. * @private
  211. */
  212. inactiveOtherPoints: true,
  213. /**
  214. * Set options on specific levels. Takes precedence over series options,
  215. * but not node and link options.
  216. *
  217. * @sample highcharts/demo/sunburst
  218. * Sunburst chart
  219. *
  220. * @type {Array<*>}
  221. * @since 7.1.0
  222. * @apioption plotOptions.sankey.levels
  223. */
  224. /**
  225. * Can set `borderColor` on all nodes which lay on the same level.
  226. *
  227. * @type {Highcharts.ColorString}
  228. * @apioption plotOptions.sankey.levels.borderColor
  229. */
  230. /**
  231. * Can set `borderWidth` on all nodes which lay on the same level.
  232. *
  233. * @type {number}
  234. * @apioption plotOptions.sankey.levels.borderWidth
  235. */
  236. /**
  237. * Can set `color` on all nodes which lay on the same level.
  238. *
  239. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  240. * @apioption plotOptions.sankey.levels.color
  241. */
  242. /**
  243. * Can set `colorByPoint` on all nodes which lay on the same level.
  244. *
  245. * @type {boolean}
  246. * @default true
  247. * @apioption plotOptions.sankey.levels.colorByPoint
  248. */
  249. /**
  250. * Can set `dataLabels` on all points which lay on the same level.
  251. *
  252. * @extends plotOptions.sankey.dataLabels
  253. * @apioption plotOptions.sankey.levels.dataLabels
  254. */
  255. /**
  256. * Decides which level takes effect from the options set in the levels
  257. * object.
  258. *
  259. * @type {number}
  260. * @apioption plotOptions.sankey.levels.level
  261. */
  262. /**
  263. * Can set `linkOpacity` on all points which lay on the same level.
  264. *
  265. * @type {number}
  266. * @default 0.5
  267. * @apioption plotOptions.sankey.levels.linkOpacity
  268. */
  269. /**
  270. * Can set `states` on all nodes and points which lay on the same level.
  271. *
  272. * @extends plotOptions.sankey.states
  273. * @apioption plotOptions.sankey.levels.states
  274. */
  275. /**
  276. * Opacity for the links between nodes in the sankey diagram.
  277. *
  278. * @private
  279. */
  280. linkOpacity: 0.5,
  281. /**
  282. * The minimal width for a line of a sankey. By default,
  283. * 0 values are not shown.
  284. *
  285. * @sample highcharts/plotoptions/sankey-minlinkwidth
  286. * Sankey diagram with minimal link height
  287. *
  288. * @type {number}
  289. * @since 7.1.3
  290. * @default 0
  291. * @apioption plotOptions.sankey.minLinkWidth
  292. *
  293. * @private
  294. */
  295. minLinkWidth: 0,
  296. /**
  297. * The pixel width of each node in a sankey diagram or dependency wheel,
  298. * or the height in case the chart is inverted.
  299. *
  300. * @private
  301. */
  302. nodeWidth: 20,
  303. /**
  304. * The padding between nodes in a sankey diagram or dependency wheel, in
  305. * pixels.
  306. *
  307. * If the number of nodes is so great that it is possible to lay them
  308. * out within the plot area with the given `nodePadding`, they will be
  309. * rendered with a smaller padding as a strategy to avoid overflow.
  310. *
  311. * @private
  312. */
  313. nodePadding: 10,
  314. showInLegend: false,
  315. states: {
  316. hover: {
  317. /**
  318. * Opacity for the links between nodes in the sankey diagram in
  319. * hover mode.
  320. */
  321. linkOpacity: 1
  322. },
  323. /**
  324. * The opposite state of a hover for a single point node/link.
  325. *
  326. * @declare Highcharts.SeriesStatesInactiveOptionsObject
  327. */
  328. inactive: {
  329. /**
  330. * Opacity for the links between nodes in the sankey diagram in
  331. * inactive mode.
  332. */
  333. linkOpacity: 0.1,
  334. /**
  335. * Opacity of inactive markers.
  336. *
  337. * @type {number}
  338. * @apioption plotOptions.series.states.inactive.opacity
  339. */
  340. opacity: 0.1,
  341. /**
  342. * Animation when not hovering over the marker.
  343. *
  344. * @type {boolean|Highcharts.AnimationOptionsObject}
  345. * @apioption plotOptions.series.states.inactive.animation
  346. */
  347. animation: {
  348. /** @internal */
  349. duration: 50
  350. }
  351. }
  352. },
  353. tooltip: {
  354. /**
  355. * A callback for defining the format for _nodes_ in the chart's
  356. * tooltip, as opposed to links.
  357. *
  358. * @type {Highcharts.FormatterCallbackFunction<Highcharts.SankeyNodeObject>}
  359. * @since 6.0.2
  360. * @apioption plotOptions.sankey.tooltip.nodeFormatter
  361. */
  362. /**
  363. * Whether the tooltip should follow the pointer or stay fixed on
  364. * the item.
  365. */
  366. followPointer: true,
  367. headerFormat: '<span style="font-size: 10px">{series.name}</span><br/>',
  368. pointFormat: '{point.fromNode.name} \u2192 {point.toNode.name}: <b>{point.weight}</b><br/>',
  369. /**
  370. * The
  371. * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  372. * specifying what to show for _nodes_ in tooltip of a diagram
  373. * series, as opposed to links.
  374. */
  375. nodeFormat: '{point.name}: <b>{point.sum}</b><br/>'
  376. }
  377. }, {
  378. isCartesian: false,
  379. invertable: true,
  380. forceDL: true,
  381. orderNodes: true,
  382. pointArrayMap: ['from', 'to'],
  383. // Create a single node that holds information on incoming and outgoing
  384. // links.
  385. createNode: H.NodesMixin.createNode,
  386. searchPoint: H.noop,
  387. setData: H.NodesMixin.setData,
  388. destroy: H.NodesMixin.destroy,
  389. /* eslint-disable valid-jsdoc */
  390. /**
  391. * Overridable function to get node padding, overridden in dependency
  392. * wheel series type.
  393. * @private
  394. */
  395. getNodePadding: function () {
  396. var nodePadding = this.options.nodePadding || 0;
  397. // If the number of columns is so great that they will overflow with
  398. // the given nodePadding, we sacrifice the padding in order to
  399. // render all nodes within the plot area (#11917).
  400. if (this.nodeColumns) {
  401. var maxLength = this.nodeColumns.reduce(function (acc, col) { return Math.max(acc, col.length); }, 0);
  402. if (maxLength * nodePadding > this.chart.plotSizeY) {
  403. nodePadding = this.chart.plotSizeY / maxLength;
  404. }
  405. }
  406. return nodePadding;
  407. },
  408. /**
  409. * Create a node column.
  410. * @private
  411. */
  412. createNodeColumn: function () {
  413. var series = this, chart = this.chart, column = [];
  414. column.sum = function () {
  415. return this.reduce(function (sum, node) {
  416. return sum + node.getSum();
  417. }, 0);
  418. };
  419. // Get the offset in pixels of a node inside the column.
  420. column.offset = function (node, factor) {
  421. var offset = 0, totalNodeOffset, nodePadding = series.nodePadding;
  422. for (var i = 0; i < column.length; i++) {
  423. var sum = column[i].getSum();
  424. var height = Math.max(sum * factor, series.options.minLinkWidth);
  425. if (sum) {
  426. totalNodeOffset = height + nodePadding;
  427. }
  428. else {
  429. // If node sum equals 0 nodePadding is missed #12453
  430. totalNodeOffset = 0;
  431. }
  432. if (column[i] === node) {
  433. return {
  434. relativeTop: offset + relativeLength(node.options.offset || 0, totalNodeOffset)
  435. };
  436. }
  437. offset += totalNodeOffset;
  438. }
  439. };
  440. // Get the top position of the column in pixels.
  441. column.top = function (factor) {
  442. var nodePadding = series.nodePadding;
  443. var height = this.reduce(function (height, node) {
  444. if (height > 0) {
  445. height += nodePadding;
  446. }
  447. var nodeHeight = Math.max(node.getSum() * factor, series.options.minLinkWidth);
  448. height += nodeHeight;
  449. return height;
  450. }, 0);
  451. return (chart.plotSizeY - height) / 2;
  452. };
  453. return column;
  454. },
  455. /**
  456. * Create node columns by analyzing the nodes and the relations between
  457. * incoming and outgoing links.
  458. * @private
  459. */
  460. createNodeColumns: function () {
  461. var columns = [];
  462. this.nodes.forEach(function (node) {
  463. var fromColumn = -1, fromNode, i, point;
  464. if (!defined(node.options.column)) {
  465. // No links to this node, place it left
  466. if (node.linksTo.length === 0) {
  467. node.column = 0;
  468. // There are incoming links, place it to the right of the
  469. // highest order column that links to this one.
  470. }
  471. else {
  472. for (i = 0; i < node.linksTo.length; i++) {
  473. point = node.linksTo[0];
  474. if (point.fromNode.column > fromColumn) {
  475. fromNode = point.fromNode;
  476. fromColumn = fromNode.column;
  477. }
  478. }
  479. node.column = fromColumn + 1;
  480. // Hanging layout for organization chart
  481. if (fromNode &&
  482. fromNode.options.layout === 'hanging') {
  483. node.hangsFrom = fromNode;
  484. i = -1; // Reuse existing variable i
  485. find(fromNode.linksFrom, function (link, index) {
  486. var found = link.toNode === node;
  487. if (found) {
  488. i = index;
  489. }
  490. return found;
  491. });
  492. node.column += i;
  493. }
  494. }
  495. }
  496. if (!columns[node.column]) {
  497. columns[node.column] = this.createNodeColumn();
  498. }
  499. columns[node.column].push(node);
  500. }, this);
  501. // Fill in empty columns (#8865)
  502. for (var i = 0; i < columns.length; i++) {
  503. if (typeof columns[i] === 'undefined') {
  504. columns[i] = this.createNodeColumn();
  505. }
  506. }
  507. return columns;
  508. },
  509. /**
  510. * Define hasData function for non-cartesian series.
  511. * @private
  512. * @return {boolean}
  513. * Returns true if the series has points at all.
  514. */
  515. hasData: function () {
  516. return !!this.processedXData.length; // != 0
  517. },
  518. /**
  519. * Return the presentational attributes.
  520. * @private
  521. */
  522. pointAttribs: function (point, state) {
  523. var series = this, level = point.isNode ? point.level : point.fromNode.level, levelOptions = series.mapOptionsToLevel[level || 0] || {}, options = point.options, stateOptions = (levelOptions.states && levelOptions.states[state]) || {}, values = [
  524. 'colorByPoint', 'borderColor', 'borderWidth', 'linkOpacity'
  525. ].reduce(function (obj, key) {
  526. obj[key] = pick(stateOptions[key], options[key], levelOptions[key], series.options[key]);
  527. return obj;
  528. }, {}), color = pick(stateOptions.color, options.color, values.colorByPoint ? point.color : levelOptions.color);
  529. // Node attributes
  530. if (point.isNode) {
  531. return {
  532. fill: color,
  533. stroke: values.borderColor,
  534. 'stroke-width': values.borderWidth
  535. };
  536. }
  537. // Link attributes
  538. return {
  539. fill: Color.parse(color).setOpacity(values.linkOpacity).get()
  540. };
  541. },
  542. /**
  543. * Extend generatePoints by adding the nodes, which are Point objects
  544. * but pushed to the this.nodes array.
  545. * @private
  546. */
  547. generatePoints: function () {
  548. H.NodesMixin.generatePoints.apply(this, arguments);
  549. /**
  550. * Order the nodes, starting with the root node(s). (#9818)
  551. * @private
  552. */
  553. function order(node, level) {
  554. // Prevents circular recursion:
  555. if (typeof node.level === 'undefined') {
  556. node.level = level;
  557. node.linksFrom.forEach(function (link) {
  558. if (link.toNode) {
  559. order(link.toNode, level + 1);
  560. }
  561. });
  562. }
  563. }
  564. if (this.orderNodes) {
  565. this.nodes
  566. // Identify the root node(s)
  567. .filter(function (node) {
  568. return node.linksTo.length === 0;
  569. })
  570. // Start by the root node(s) and recursively set the level
  571. // on all following nodes.
  572. .forEach(function (node) {
  573. order(node, 0);
  574. });
  575. stableSort(this.nodes, function (a, b) {
  576. return a.level - b.level;
  577. });
  578. }
  579. },
  580. /**
  581. * Run translation operations for one node.
  582. * @private
  583. */
  584. translateNode: function (node, column) {
  585. var translationFactor = this.translationFactor, chart = this.chart, options = this.options, sum = node.getSum(), height = Math.max(Math.round(sum * translationFactor), this.options.minLinkWidth), crisp = Math.round(options.borderWidth) % 2 / 2, nodeOffset = column.offset(node, translationFactor), fromNodeTop = Math.floor(pick(nodeOffset.absoluteTop, (column.top(translationFactor) +
  586. nodeOffset.relativeTop))) + crisp, left = Math.floor(this.colDistance * node.column +
  587. options.borderWidth / 2) + crisp, nodeLeft = chart.inverted ?
  588. chart.plotSizeX - left :
  589. left, nodeWidth = Math.round(this.nodeWidth);
  590. node.sum = sum;
  591. // If node sum is 0, don't render the rect #12453
  592. if (sum) {
  593. // Draw the node
  594. node.shapeType = 'rect';
  595. node.nodeX = nodeLeft;
  596. node.nodeY = fromNodeTop;
  597. if (!chart.inverted) {
  598. node.shapeArgs = {
  599. x: nodeLeft,
  600. y: fromNodeTop,
  601. width: node.options.width || options.width || nodeWidth,
  602. height: node.options.height || options.height || height
  603. };
  604. }
  605. else {
  606. node.shapeArgs = {
  607. x: nodeLeft - nodeWidth,
  608. y: chart.plotSizeY - fromNodeTop - height,
  609. width: node.options.height || options.height || nodeWidth,
  610. height: node.options.width || options.width || height
  611. };
  612. }
  613. node.shapeArgs.display = node.hasShape() ? '' : 'none';
  614. // Calculate data label options for the point
  615. node.dlOptions = getDLOptions({
  616. level: this.mapOptionsToLevel[node.level],
  617. optionsPoint: node.options
  618. });
  619. // Pass test in drawPoints
  620. node.plotY = 1;
  621. // Set the anchor position for tooltips
  622. node.tooltipPos = chart.inverted ? [
  623. chart.plotSizeY - node.shapeArgs.y - node.shapeArgs.height / 2,
  624. chart.plotSizeX - node.shapeArgs.x - node.shapeArgs.width / 2
  625. ] : [
  626. node.shapeArgs.x + node.shapeArgs.width / 2,
  627. node.shapeArgs.y + node.shapeArgs.height / 2
  628. ];
  629. }
  630. else {
  631. node.dlOptions = {
  632. enabled: false
  633. };
  634. }
  635. },
  636. /**
  637. * Run translation operations for one link.
  638. * @private
  639. */
  640. translateLink: function (point) {
  641. var getY = function (node, fromOrTo) {
  642. var _a;
  643. var linkTop = (node.offset(point, fromOrTo) *
  644. translationFactor);
  645. var y = Math.min(node.nodeY + linkTop,
  646. // Prevent links from spilling below the node (#12014)
  647. node.nodeY + ((_a = node.shapeArgs) === null || _a === void 0 ? void 0 : _a.height) - linkHeight);
  648. return y;
  649. };
  650. var fromNode = point.fromNode, toNode = point.toNode, chart = this.chart, translationFactor = this.translationFactor, linkHeight = Math.max(point.weight * translationFactor, this.options.minLinkWidth), options = this.options, curvy = ((chart.inverted ? -this.colDistance : this.colDistance) *
  651. options.curveFactor), fromY = getY(fromNode, 'linksFrom'), toY = getY(toNode, 'linksTo'), nodeLeft = fromNode.nodeX, nodeW = this.nodeWidth, right = toNode.column * this.colDistance, outgoing = point.outgoing, straight = right > nodeLeft + nodeW;
  652. if (chart.inverted) {
  653. fromY = chart.plotSizeY - fromY;
  654. toY = (chart.plotSizeY || 0) - toY;
  655. right = chart.plotSizeX - right;
  656. nodeW = -nodeW;
  657. linkHeight = -linkHeight;
  658. straight = nodeLeft > right;
  659. }
  660. point.shapeType = 'path';
  661. point.linkBase = [
  662. fromY,
  663. fromY + linkHeight,
  664. toY,
  665. toY + linkHeight
  666. ];
  667. // Links going from left to right
  668. if (straight && typeof toY === 'number') {
  669. point.shapeArgs = {
  670. d: [
  671. ['M', nodeLeft + nodeW, fromY],
  672. [
  673. 'C',
  674. nodeLeft + nodeW + curvy,
  675. fromY,
  676. right - curvy,
  677. toY,
  678. right,
  679. toY
  680. ],
  681. ['L', right + (outgoing ? nodeW : 0), toY + linkHeight / 2],
  682. ['L', right, toY + linkHeight],
  683. [
  684. 'C',
  685. right - curvy,
  686. toY + linkHeight,
  687. nodeLeft + nodeW + curvy,
  688. fromY + linkHeight,
  689. nodeLeft + nodeW, fromY + linkHeight
  690. ],
  691. ['Z']
  692. ]
  693. };
  694. // Experimental: Circular links pointing backwards. In
  695. // v6.1.0 this breaks the rendering completely, so even
  696. // this experimental rendering is an improvement. #8218.
  697. // @todo
  698. // - Make room for the link in the layout
  699. // - Automatically determine if the link should go up or
  700. // down.
  701. }
  702. else if (typeof toY === 'number') {
  703. var bend = 20, vDist = chart.plotHeight - fromY - linkHeight, x1 = right - bend - linkHeight, x2 = right - bend, x3 = right, x4 = nodeLeft + nodeW, x5 = x4 + bend, x6 = x5 + linkHeight, fy1 = fromY, fy2 = fromY + linkHeight, fy3 = fy2 + bend, y4 = fy3 + vDist, y5 = y4 + bend, y6 = y5 + linkHeight, ty1 = toY, ty2 = ty1 + linkHeight, ty3 = ty2 + bend, cfy1 = fy2 - linkHeight * 0.7, cy2 = y5 + linkHeight * 0.7, cty1 = ty2 - linkHeight * 0.7, cx1 = x3 - linkHeight * 0.7, cx2 = x4 + linkHeight * 0.7;
  704. point.shapeArgs = {
  705. d: [
  706. ['M', x4, fy1],
  707. ['C', cx2, fy1, x6, cfy1, x6, fy3],
  708. ['L', x6, y4],
  709. ['C', x6, cy2, cx2, y6, x4, y6],
  710. ['L', x3, y6],
  711. ['C', cx1, y6, x1, cy2, x1, y4],
  712. ['L', x1, ty3],
  713. ['C', x1, cty1, cx1, ty1, x3, ty1],
  714. ['L', x3, ty2],
  715. ['C', x2, ty2, x2, ty2, x2, ty3],
  716. ['L', x2, y4],
  717. ['C', x2, y5, x2, y5, x3, y5],
  718. ['L', x4, y5],
  719. ['C', x5, y5, x5, y5, x5, y4],
  720. ['L', x5, fy3],
  721. ['C', x5, fy2, x5, fy2, x4, fy2],
  722. ['Z']
  723. ]
  724. };
  725. }
  726. // Place data labels in the middle
  727. point.dlBox = {
  728. x: nodeLeft + (right - nodeLeft + nodeW) / 2,
  729. y: fromY + (toY - fromY) / 2,
  730. height: linkHeight,
  731. width: 0
  732. };
  733. // And set the tooltip anchor in the middle
  734. point.tooltipPos = chart.inverted ? [
  735. chart.plotSizeY - point.dlBox.y - linkHeight / 2,
  736. chart.plotSizeX - point.dlBox.x
  737. ] : [
  738. point.dlBox.x,
  739. point.dlBox.y + linkHeight / 2
  740. ];
  741. // Pass test in drawPoints
  742. point.y = point.plotY = 1;
  743. if (!point.color) {
  744. point.color = fromNode.color;
  745. }
  746. },
  747. /**
  748. * Run pre-translation by generating the nodeColumns.
  749. * @private
  750. */
  751. translate: function () {
  752. var _this = this;
  753. // Get the translation factor needed for each column to fill up the
  754. // plot height
  755. var getColumnTranslationFactor = function (column) {
  756. var nodes = column.slice();
  757. var minLinkWidth = _this.options.minLinkWidth || 0;
  758. var exceedsMinLinkWidth;
  759. var factor = 0;
  760. var i;
  761. var remainingHeight = chart.plotSizeY -
  762. options.borderWidth - (column.length - 1) * series.nodePadding;
  763. // Because the minLinkWidth option doesn't obey the direct
  764. // translation, we need to run translation iteratively, check
  765. // node heights, remove those nodes affected by minLinkWidth,
  766. // check again, etc.
  767. while (column.length) {
  768. factor = remainingHeight / column.sum();
  769. exceedsMinLinkWidth = false;
  770. i = column.length;
  771. while (i--) {
  772. if (column[i].getSum() * factor < minLinkWidth) {
  773. column.splice(i, 1);
  774. remainingHeight -= minLinkWidth;
  775. exceedsMinLinkWidth = true;
  776. }
  777. }
  778. if (!exceedsMinLinkWidth) {
  779. break;
  780. }
  781. }
  782. // Re-insert original nodes
  783. column.length = 0;
  784. nodes.forEach(function (node) { return column.push(node); });
  785. return factor;
  786. };
  787. if (!this.processedXData) {
  788. this.processData();
  789. }
  790. this.generatePoints();
  791. this.nodeColumns = this.createNodeColumns();
  792. this.nodeWidth = relativeLength(this.options.nodeWidth, this.chart.plotSizeX);
  793. var series = this, chart = this.chart, options = this.options, nodeWidth = this.nodeWidth, nodeColumns = this.nodeColumns;
  794. this.nodePadding = this.getNodePadding();
  795. // Find out how much space is needed. Base it on the translation
  796. // factor of the most spaceous column.
  797. this.translationFactor = nodeColumns.reduce(function (translationFactor, column) { return Math.min(translationFactor, getColumnTranslationFactor(column)); }, Infinity);
  798. this.colDistance =
  799. (chart.plotSizeX - nodeWidth -
  800. options.borderWidth) / Math.max(1, nodeColumns.length - 1);
  801. // Calculate level options used in sankey and organization
  802. series.mapOptionsToLevel = getLevelOptions({
  803. // NOTE: if support for allowTraversingTree is added, then from
  804. // should be the level of the root node.
  805. from: 1,
  806. levels: options.levels,
  807. to: nodeColumns.length - 1,
  808. defaults: {
  809. borderColor: options.borderColor,
  810. borderRadius: options.borderRadius,
  811. borderWidth: options.borderWidth,
  812. color: series.color,
  813. colorByPoint: options.colorByPoint,
  814. // NOTE: if support for allowTraversingTree is added, then
  815. // levelIsConstant should be optional.
  816. levelIsConstant: true,
  817. linkColor: options.linkColor,
  818. linkLineWidth: options.linkLineWidth,
  819. linkOpacity: options.linkOpacity,
  820. states: options.states
  821. }
  822. });
  823. // First translate all nodes so we can use them when drawing links
  824. nodeColumns.forEach(function (column) {
  825. column.forEach(function (node) {
  826. series.translateNode(node, column);
  827. });
  828. }, this);
  829. // Then translate links
  830. this.nodes.forEach(function (node) {
  831. // Translate the links from this node
  832. node.linksFrom.forEach(function (linkPoint) {
  833. // If weight is 0 - don't render the link path #12453,
  834. // render null points (for organization chart)
  835. if ((linkPoint.weight || linkPoint.isNull) && linkPoint.to) {
  836. series.translateLink(linkPoint);
  837. linkPoint.allowShadow = false;
  838. }
  839. });
  840. });
  841. },
  842. /**
  843. * Extend the render function to also render this.nodes together with
  844. * the points.
  845. * @private
  846. */
  847. render: function () {
  848. var points = this.points;
  849. this.points = this.points.concat(this.nodes || []);
  850. H.seriesTypes.column.prototype.render.call(this);
  851. this.points = points;
  852. },
  853. /* eslint-enable valid-jsdoc */
  854. animate: H.Series.prototype.animate
  855. }, {
  856. applyOptions: function (options, x) {
  857. Point.prototype.applyOptions.call(this, options, x);
  858. // Treat point.level as a synonym of point.column
  859. if (defined(this.options.level)) {
  860. this.options.column = this.column = this.options.level;
  861. }
  862. return this;
  863. },
  864. setState: H.NodesMixin.setNodeState,
  865. getClassName: function () {
  866. return (this.isNode ? 'highcharts-node ' : 'highcharts-link ') +
  867. Point.prototype.getClassName.call(this);
  868. },
  869. isValid: function () {
  870. return this.isNode || typeof this.weight === 'number';
  871. }
  872. });
  873. /**
  874. * A `sankey` series. If the [type](#series.sankey.type) option is not
  875. * specified, it is inherited from [chart.type](#chart.type).
  876. *
  877. * @extends series,plotOptions.sankey
  878. * @excluding animationLimit, boostBlending, boostThreshold, borderColor,
  879. * borderRadius, borderWidth, crisp, cropThreshold, dataParser,
  880. * dataURL, depth, dragDrop, edgeColor, edgeWidth,
  881. * findNearestPointBy, getExtremesFromAll, grouping, groupPadding,
  882. * groupZPadding, label, maxPointWidth, negativeColor, pointInterval,
  883. * pointIntervalUnit, pointPadding, pointPlacement, pointRange,
  884. * pointStart, pointWidth, shadow, softThreshold, stacking,
  885. * threshold, zoneAxis, zones, dataSorting
  886. * @product highcharts
  887. * @requires modules/sankey
  888. * @apioption series.sankey
  889. */
  890. /**
  891. * A collection of options for the individual nodes. The nodes in a sankey
  892. * diagram are auto-generated instances of `Highcharts.Point`, but options can
  893. * be applied here and linked by the `id`.
  894. *
  895. * @sample highcharts/css/sankey/
  896. * Sankey diagram with node options
  897. *
  898. * @declare Highcharts.SeriesSankeyNodesOptionsObject
  899. * @type {Array<*>}
  900. * @product highcharts
  901. * @apioption series.sankey.nodes
  902. */
  903. /**
  904. * The id of the auto-generated node, refering to the `from` or `to` setting of
  905. * the link.
  906. *
  907. * @type {string}
  908. * @product highcharts
  909. * @apioption series.sankey.nodes.id
  910. */
  911. /**
  912. * The color of the auto generated node.
  913. *
  914. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  915. * @product highcharts
  916. * @apioption series.sankey.nodes.color
  917. */
  918. /**
  919. * The color index of the auto generated node, especially for use in styled
  920. * mode.
  921. *
  922. * @type {number}
  923. * @product highcharts
  924. * @apioption series.sankey.nodes.colorIndex
  925. */
  926. /**
  927. * An optional column index of where to place the node. The default behaviour is
  928. * to place it next to the preceding node. Note that this option name is
  929. * counter intuitive in inverted charts, like for example an organization chart
  930. * rendered top down. In this case the "columns" are horizontal.
  931. *
  932. * @sample highcharts/plotoptions/sankey-node-column/
  933. * Specified node column
  934. *
  935. * @type {number}
  936. * @since 6.0.5
  937. * @product highcharts
  938. * @apioption series.sankey.nodes.column
  939. */
  940. /**
  941. * Individual data label for each node. The options are the same as
  942. * the ones for [series.sankey.dataLabels](#series.sankey.dataLabels).
  943. *
  944. * @extends plotOptions.sankey.dataLabels
  945. * @apioption series.sankey.nodes.dataLabels
  946. */
  947. /**
  948. * An optional level index of where to place the node. The default behaviour is
  949. * to place it next to the preceding node. Alias of `nodes.column`, but in
  950. * inverted sankeys and org charts, the levels are laid out as rows.
  951. *
  952. * @type {number}
  953. * @since 7.1.0
  954. * @product highcharts
  955. * @apioption series.sankey.nodes.level
  956. */
  957. /**
  958. * The name to display for the node in data labels and tooltips. Use this when
  959. * the name is different from the `id`. Where the id must be unique for each
  960. * node, this is not necessary for the name.
  961. *
  962. * @sample highcharts/css/sankey/
  963. * Sankey diagram with node options
  964. *
  965. * @type {string}
  966. * @product highcharts
  967. * @apioption series.sankey.nodes.name
  968. */
  969. /**
  970. * In a horizontal layout, the vertical offset of a node in terms of weight.
  971. * Positive values shift the node downwards, negative shift it upwards. In a
  972. * vertical layout, like organization chart, the offset is horizontal.
  973. *
  974. * If a percantage string is given, the node is offset by the percentage of the
  975. * node size plus `nodePadding`.
  976. *
  977. * @sample highcharts/plotoptions/sankey-node-column/
  978. * Specified node offset
  979. *
  980. * @type {number|string}
  981. * @default 0
  982. * @since 6.0.5
  983. * @product highcharts
  984. * @apioption series.sankey.nodes.offset
  985. */
  986. /**
  987. * An array of data points for the series. For the `sankey` series type,
  988. * points can be given in the following way:
  989. *
  990. * An array of objects with named values. The following snippet shows only a
  991. * few settings, see the complete options set below. If the total number of data
  992. * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
  993. * this option is not available.
  994. *
  995. * ```js
  996. * data: [{
  997. * from: 'Category1',
  998. * to: 'Category2',
  999. * weight: 2
  1000. * }, {
  1001. * from: 'Category1',
  1002. * to: 'Category3',
  1003. * weight: 5
  1004. * }]
  1005. * ```
  1006. *
  1007. * @sample {highcharts} highcharts/series/data-array-of-objects/
  1008. * Config objects
  1009. *
  1010. * @declare Highcharts.SeriesSankeyPointOptionsObject
  1011. * @type {Array<*>}
  1012. * @extends series.line.data
  1013. * @excluding dragDrop, drilldown, marker, x, y
  1014. * @product highcharts
  1015. * @apioption series.sankey.data
  1016. */
  1017. /**
  1018. * The color for the individual _link_. By default, the link color is the same
  1019. * as the node it extends from. The `series.fillOpacity` option also applies to
  1020. * the points, so when setting a specific link color, consider setting the
  1021. * `fillOpacity` to 1.
  1022. *
  1023. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1024. * @product highcharts
  1025. * @apioption series.sankey.data.color
  1026. */
  1027. /**
  1028. * @type {Highcharts.SeriesSankeyDataLabelsOptionsObject|Array<Highcharts.SeriesSankeyDataLabelsOptionsObject>}
  1029. * @product highcharts
  1030. * @apioption series.sankey.data.dataLabels
  1031. */
  1032. /**
  1033. * The node that the link runs from.
  1034. *
  1035. * @type {string}
  1036. * @product highcharts
  1037. * @apioption series.sankey.data.from
  1038. */
  1039. /**
  1040. * The node that the link runs to.
  1041. *
  1042. * @type {string}
  1043. * @product highcharts
  1044. * @apioption series.sankey.data.to
  1045. */
  1046. /**
  1047. * Whether the link goes out of the system.
  1048. *
  1049. * @sample highcharts/plotoptions/sankey-outgoing
  1050. * Sankey chart with outgoing links
  1051. *
  1052. * @type {boolean}
  1053. * @default false
  1054. * @product highcharts
  1055. * @apioption series.sankey.data.outgoing
  1056. */
  1057. /**
  1058. * The weight of the link.
  1059. *
  1060. * @type {number|null}
  1061. * @product highcharts
  1062. * @apioption series.sankey.data.weight
  1063. */
  1064. ''; // adds doclets above to transpiled file