sankey.src.js 34 KB

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