sankey.src.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. * Sankey diagram module
  4. *
  5. * (c) 2010-2017 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. (function (H) {
  18. /**
  19. * Sankey diagram module
  20. *
  21. * (c) 2010-2017 Torstein Honsi
  22. *
  23. * License: www.highcharts.com/license
  24. */
  25. var defined = H.defined,
  26. each = H.each,
  27. extend = H.extend,
  28. seriesType = H.seriesType,
  29. pick = H.pick,
  30. Point = H.Point;
  31. /**
  32. * A sankey diagram is a type of flow diagram, in which the width of the
  33. * link between two nodes is shown proportionally to the flow quantity.
  34. *
  35. * @extends {plotOptions.column}
  36. * @product highcharts
  37. * @sample highcharts/demo/sankey-diagram/
  38. * Sankey diagram
  39. * @sample highcharts/plotoptions/sankey-inverted/
  40. * Inverted sankey diagram
  41. * @sample highcharts/plotoptions/sankey-outgoing
  42. * Sankey diagram with outgoing links
  43. * @since 6.0.0
  44. * @excluding animationLimit,boostThreshold,borderColor,borderRadius,
  45. * borderWidth,crisp,cropThreshold,depth,edgeColor,edgeWidth,
  46. * findNearestPointBy,grouping,groupPadding,groupZPadding,
  47. * maxPointWidth,negativeColor,pointInterval,pointIntervalUnit,
  48. * pointPadding,pointPlacement,pointRange,pointStart,pointWidth,
  49. * shadow,softThreshold,stacking,threshold,zoneAxis,zones
  50. * @optionparent plotOptions.sankey
  51. */
  52. seriesType('sankey', 'column', {
  53. colorByPoint: true,
  54. /**
  55. * Higher numbers makes the links in a sankey diagram render more curved.
  56. * A `curveFactor` of 0 makes the lines straight.
  57. */
  58. curveFactor: 0.33,
  59. /**
  60. * Options for the data labels appearing on top of the nodes and links. For
  61. * sankey charts, data labels are visible for the nodes by default, but
  62. * hidden for links. This is controlled by modifying the `nodeFormat`, and
  63. * the `format` that applies to links and is an empty string by default.
  64. */
  65. dataLabels: {
  66. enabled: true,
  67. backgroundColor: 'none', // enable padding
  68. crop: false,
  69. /**
  70. * The [format string](http://www.highcharts.com/docs/chart-
  71. * concepts/labels-and-string-formatting) specifying what to show
  72. * for _nodes_ in the sankey diagram. By default the
  73. * `nodeFormatter` returns `{point.name}`.
  74. *
  75. * @type {String}
  76. */
  77. nodeFormat: undefined,
  78. /**
  79. * Callback to format data labels for _nodes_ in the sankey diagram.
  80. * The `nodeFormat` option takes precedence over the `nodeFormatter`.
  81. *
  82. * @type {Function}
  83. * @since 6.0.2
  84. */
  85. nodeFormatter: function () {
  86. return this.point.name;
  87. },
  88. /**
  89. * The [format string](http://www.highcharts.com/docs/chart-
  90. * concepts/labels-and-string-formatting) specifying what to show for
  91. * _links_ in the sankey diagram. Defaults to an empty string returned
  92. * from the `formatter`, in effect disabling the labels.
  93. */
  94. format: undefined,
  95. /**
  96. * Callback to format data labels for _links_ in the sankey diagram.
  97. * The `format` option takes precedence over the `formatter`.
  98. *
  99. * @since 6.0.2
  100. */
  101. formatter: function () {
  102. return '';
  103. },
  104. inside: true
  105. },
  106. /**
  107. * Opacity for the links between nodes in the sankey diagram.
  108. */
  109. linkOpacity: 0.5,
  110. /**
  111. * The pixel width of each node in a sankey diagram, or the height in case
  112. * the chart is inverted.
  113. */
  114. nodeWidth: 20,
  115. /**
  116. * The padding between nodes in a sankey diagram, in pixels.
  117. */
  118. nodePadding: 10,
  119. showInLegend: false,
  120. states: {
  121. hover: {
  122. /**
  123. * Opacity for the links between nodes in the sankey diagram in
  124. * hover mode.
  125. */
  126. linkOpacity: 1
  127. }
  128. },
  129. tooltip: {
  130. /**
  131. * A callback for defining the format for _nodes_ in the sankey chart's
  132. * tooltip, as opposed to links.
  133. *
  134. * @type {Function}
  135. * @since 6.0.2
  136. * @apioption plotOptions.sankey.tooltip.nodeFormatter
  137. */
  138. /**
  139. * Whether the tooltip should follow the pointer or stay fixed on the
  140. * item.
  141. */
  142. followPointer: true,
  143. headerFormat:
  144. '<span style="font-size: 0.85em">{series.name}</span><br/>',
  145. pointFormat: '{point.fromNode.name} \u2192 {point.toNode.name}: <b>{point.weight}</b><br/>',
  146. /**
  147. * The [format string](http://www.highcharts.com/docs/chart-
  148. * concepts/labels-and-string-formatting) specifying what to
  149. * show for _nodes_ in tooltip
  150. * of a sankey diagram series, as opposed to links.
  151. */
  152. nodeFormat: '{point.name}: <b>{point.sum}</b><br/>'
  153. }
  154. }, {
  155. isCartesian: false,
  156. forceDL: true,
  157. /**
  158. * Create a single node that holds information on incoming and outgoing
  159. * links.
  160. */
  161. createNode: function (id) {
  162. function findById(nodes, id) {
  163. return H.find(nodes, function (node) {
  164. return node.id === id;
  165. });
  166. }
  167. var node = findById(this.nodes, id),
  168. options;
  169. if (!node) {
  170. options = this.options.nodes && findById(this.options.nodes, id);
  171. node = (new Point()).init(
  172. this,
  173. extend({
  174. className: 'highcharts-node',
  175. isNode: true,
  176. id: id,
  177. y: 1 // Pass isNull test
  178. }, options)
  179. );
  180. node.linksTo = [];
  181. node.linksFrom = [];
  182. node.formatPrefix = 'node';
  183. node.name = node.name || node.id; // for use in formats
  184. /**
  185. * Return the largest sum of either the incoming or outgoing links.
  186. */
  187. node.getSum = function () {
  188. var sumTo = 0,
  189. sumFrom = 0;
  190. each(node.linksTo, function (link) {
  191. sumTo += link.weight;
  192. });
  193. each(node.linksFrom, function (link) {
  194. sumFrom += link.weight;
  195. });
  196. return Math.max(sumTo, sumFrom);
  197. };
  198. /**
  199. * Get the offset in weight values of a point/link.
  200. */
  201. node.offset = function (point, coll) {
  202. var offset = 0;
  203. for (var i = 0; i < node[coll].length; i++) {
  204. if (node[coll][i] === point) {
  205. return offset;
  206. }
  207. offset += node[coll][i].weight;
  208. }
  209. };
  210. /**
  211. * Return true if the node has a shape, otherwise all links are
  212. * outgoing.
  213. */
  214. node.hasShape = function () {
  215. var outgoing = 0;
  216. each(node.linksTo, function (link) {
  217. if (link.outgoing) {
  218. outgoing++;
  219. }
  220. });
  221. return !node.linksTo.length || outgoing !== node.linksTo.length;
  222. };
  223. this.nodes.push(node);
  224. }
  225. return node;
  226. },
  227. /**
  228. * Create a node column.
  229. */
  230. createNodeColumn: function () {
  231. var chart = this.chart,
  232. column = [],
  233. nodePadding = this.options.nodePadding;
  234. column.sum = function () {
  235. var sum = 0;
  236. each(this, function (node) {
  237. sum += node.getSum();
  238. });
  239. return sum;
  240. };
  241. /**
  242. * Get the offset in pixels of a node inside the column.
  243. */
  244. column.offset = function (node, factor) {
  245. var offset = 0;
  246. for (var i = 0; i < column.length; i++) {
  247. if (column[i] === node) {
  248. return offset + (node.options.offset || 0);
  249. }
  250. offset += column[i].getSum() * factor + nodePadding;
  251. }
  252. };
  253. /**
  254. * Get the column height in pixels.
  255. */
  256. column.top = function (factor) {
  257. var height = 0;
  258. for (var i = 0; i < column.length; i++) {
  259. if (i > 0) {
  260. height += nodePadding;
  261. }
  262. height += column[i].getSum() * factor;
  263. }
  264. return (chart.plotSizeY - height) / 2;
  265. };
  266. return column;
  267. },
  268. /**
  269. * Create node columns by analyzing the nodes and the relations between
  270. * incoming and outgoing links.
  271. */
  272. createNodeColumns: function () {
  273. var columns = [];
  274. each(this.nodes, function (node) {
  275. var fromColumn = 0,
  276. i,
  277. point;
  278. if (!H.defined(node.options.column)) {
  279. // No links to this node, place it left
  280. if (node.linksTo.length === 0) {
  281. node.column = 0;
  282. // There are incoming links, place it to the right of the
  283. // highest order column that links to this one.
  284. } else {
  285. for (i = 0; i < node.linksTo.length; i++) {
  286. point = node.linksTo[0];
  287. if (point.fromNode.column > fromColumn) {
  288. fromColumn = point.fromNode.column;
  289. }
  290. }
  291. node.column = fromColumn + 1;
  292. }
  293. }
  294. if (!columns[node.column]) {
  295. columns[node.column] = this.createNodeColumn();
  296. }
  297. columns[node.column].push(node);
  298. }, this);
  299. return columns;
  300. },
  301. /**
  302. * Return the presentational attributes.
  303. */
  304. pointAttribs: function (point, state) {
  305. var opacity = this.options.linkOpacity;
  306. if (state) {
  307. opacity = this.options.states[state].linkOpacity || opacity;
  308. }
  309. return {
  310. fill: point.isNode ?
  311. point.color :
  312. H.color(point.color).setOpacity(opacity).get()
  313. };
  314. },
  315. /**
  316. * Extend generatePoints by adding the nodes, which are Point objects
  317. * but pushed to the this.nodes array.
  318. */
  319. generatePoints: function () {
  320. var nodeLookup = {};
  321. H.Series.prototype.generatePoints.call(this);
  322. if (!this.nodes) {
  323. this.nodes = []; // List of Point-like node items
  324. }
  325. this.colorCounter = 0;
  326. // Reset links from previous run
  327. each(this.nodes, function (node) {
  328. node.linksFrom.length = 0;
  329. node.linksTo.length = 0;
  330. });
  331. // Create the node list and set up links
  332. each(this.points, function (point) {
  333. if (defined(point.from)) {
  334. if (!nodeLookup[point.from]) {
  335. nodeLookup[point.from] = this.createNode(point.from);
  336. }
  337. nodeLookup[point.from].linksFrom.push(point);
  338. point.fromNode = nodeLookup[point.from];
  339. // Point color defaults to the fromNode's color
  340. point.color =
  341. point.options.color || nodeLookup[point.from].color;
  342. }
  343. if (defined(point.to)) {
  344. if (!nodeLookup[point.to]) {
  345. nodeLookup[point.to] = this.createNode(point.to);
  346. }
  347. nodeLookup[point.to].linksTo.push(point);
  348. point.toNode = nodeLookup[point.to];
  349. }
  350. point.name = point.name || point.id; // for use in formats
  351. }, this);
  352. },
  353. /**
  354. * Run pre-translation by generating the nodeColumns.
  355. */
  356. translate: function () {
  357. if (!this.processedXData) {
  358. this.processData();
  359. }
  360. this.generatePoints();
  361. this.nodeColumns = this.createNodeColumns();
  362. var chart = this.chart,
  363. inverted = chart.inverted,
  364. options = this.options,
  365. left = 0,
  366. nodeWidth = options.nodeWidth,
  367. nodeColumns = this.nodeColumns,
  368. colDistance = (chart.plotSizeX - nodeWidth) /
  369. (nodeColumns.length - 1),
  370. curvy = (
  371. (inverted ? -colDistance : colDistance) *
  372. options.curveFactor
  373. ),
  374. factor = Infinity;
  375. // Find out how much space is needed. Base it on the translation
  376. // factor of the most spaceous column.
  377. each(this.nodeColumns, function (column) {
  378. var height = chart.plotSizeY -
  379. (column.length - 1) * options.nodePadding;
  380. factor = Math.min(factor, height / column.sum());
  381. });
  382. each(this.nodeColumns, function (column) {
  383. each(column, function (node) {
  384. var sum = node.getSum(),
  385. height = sum * factor,
  386. fromNodeTop = (
  387. column.top(factor) +
  388. column.offset(node, factor)
  389. ),
  390. nodeLeft = inverted ?
  391. chart.plotSizeX - left :
  392. left;
  393. node.sum = sum;
  394. // Draw the node
  395. node.shapeType = 'rect';
  396. if (!inverted) {
  397. node.shapeArgs = {
  398. x: nodeLeft,
  399. y: fromNodeTop,
  400. width: nodeWidth,
  401. height: height
  402. };
  403. } else {
  404. node.shapeArgs = {
  405. x: nodeLeft - nodeWidth,
  406. y: chart.plotSizeY - fromNodeTop - height,
  407. width: nodeWidth,
  408. height: height
  409. };
  410. }
  411. node.shapeArgs.display = node.hasShape() ? '' : 'none';
  412. // Pass test in drawPoints
  413. node.plotY = 1;
  414. // Draw the links from this node
  415. each(node.linksFrom, function (point) {
  416. var linkHeight = point.weight * factor,
  417. fromLinkTop = node.offset(point, 'linksFrom') *
  418. factor,
  419. fromY = fromNodeTop + fromLinkTop,
  420. toNode = point.toNode,
  421. toColTop = nodeColumns[toNode.column].top(factor),
  422. toY = (
  423. toColTop +
  424. (toNode.offset(point, 'linksTo') * factor) +
  425. nodeColumns[toNode.column].offset(
  426. toNode,
  427. factor
  428. )
  429. ),
  430. nodeW = nodeWidth,
  431. right = toNode.column * colDistance,
  432. outgoing = point.outgoing;
  433. if (inverted) {
  434. fromY = chart.plotSizeY - fromY;
  435. toY = chart.plotSizeY - toY;
  436. right = chart.plotSizeX - right;
  437. nodeW = -nodeW;
  438. linkHeight = -linkHeight;
  439. }
  440. point.shapeType = 'path';
  441. point.shapeArgs = {
  442. d: [
  443. 'M', nodeLeft + nodeW, fromY,
  444. 'C', nodeLeft + nodeW + curvy, fromY,
  445. right - curvy, toY,
  446. right, toY,
  447. 'L',
  448. right + (outgoing ? nodeW : 0),
  449. toY + linkHeight / 2,
  450. 'L',
  451. right,
  452. toY + linkHeight,
  453. 'C', right - curvy, toY + linkHeight,
  454. nodeLeft + nodeW + curvy, fromY + linkHeight,
  455. nodeLeft + nodeW, fromY + linkHeight,
  456. 'z'
  457. ]
  458. };
  459. // Place data labels in the middle
  460. point.dlBox = {
  461. x: nodeLeft + (right - nodeLeft + nodeW) / 2,
  462. y: fromY + (toY - fromY) / 2,
  463. height: linkHeight,
  464. width: 0
  465. };
  466. // Pass test in drawPoints
  467. point.y = point.plotY = 1;
  468. if (!point.color) {
  469. point.color = node.color;
  470. }
  471. });
  472. });
  473. left += colDistance;
  474. }, this);
  475. },
  476. /**
  477. * Extend the render function to also render this.nodes together with
  478. * the points.
  479. */
  480. render: function () {
  481. var points = this.points;
  482. this.points = this.points.concat(this.nodes);
  483. H.seriesTypes.column.prototype.render.call(this);
  484. this.points = points;
  485. },
  486. animate: H.Series.prototype.animate
  487. }, {
  488. getClassName: function () {
  489. return 'highcharts-link ' + Point.prototype.getClassName.call(this);
  490. },
  491. isValid: function () {
  492. return this.isNode || typeof this.weight === 'number';
  493. }
  494. });
  495. /**
  496. * A `sankey` series. If the [type](#series.sankey.type) option is not
  497. * specified, it is inherited from [chart.type](#chart.type).
  498. *
  499. * @type {Object}
  500. * @extends series,plotOptions.sankey
  501. * @excluding animationLimit,boostThreshold,borderColor,borderRadius,
  502. * borderWidth,crisp,cropThreshold,dataParser,dataURL,depth,
  503. * edgeColor,edgeWidth,findNearestPointBy,grouping,groupPadding,
  504. * groupZPadding,maxPointWidth,negativeColor,pointInterval,
  505. * pointIntervalUnit,pointPadding,pointPlacement,pointRange,
  506. * pointStart,pointWidth,shadow,softThreshold,stacking,threshold,
  507. * zoneAxis,zones
  508. * @product highcharts
  509. * @apioption series.sankey
  510. */
  511. /**
  512. * A collection of options for the individual nodes. The nodes in a sankey
  513. * diagram are auto-generated instances of `Highcharts.Point`, but options can
  514. * be applied here and linked by the `id`.
  515. *
  516. * @sample highcharts/css/sankey/
  517. * Sankey diagram with node options
  518. * @type {Array.<Object>}
  519. * @product highcharts
  520. * @apioption series.sankey.nodes
  521. */
  522. /**
  523. * The id of the auto-generated node, refering to the `from` or `to` setting of
  524. * the link.
  525. *
  526. * @type {String}
  527. * @product highcharts
  528. * @apioption series.sankey.nodes.id
  529. */
  530. /**
  531. * The color of the auto generated node.
  532. *
  533. * @type {Color}
  534. * @product highcharts
  535. * @apioption series.sankey.nodes.color
  536. */
  537. /**
  538. * The color index of the auto generated node, especially for use in styled
  539. * mode.
  540. *
  541. * @type {Number}
  542. * @product highcharts
  543. * @apioption series.sankey.nodes.colorIndex
  544. */
  545. /**
  546. * An optional column index of where to place the node. The default behaviour is
  547. * to place it next to the preceding node.
  548. *
  549. * @type {Undefined|Number}
  550. * @default undefined
  551. * @sample highcharts/plotoptions/sankey-node-column/
  552. * Specified node column
  553. * @product highcharts
  554. * @since 6.0.5
  555. * @apioption series.sankey.nodes.column
  556. */
  557. /**
  558. * The name to display for the node in data labels and tooltips. Use this when
  559. * the name is different from the `id`. Where the id must be unique for each
  560. * node, this is not necessary for the name.
  561. *
  562. * @type {String}
  563. * @sample highcharts/css/sankey/
  564. * Sankey diagram with node options
  565. * @product highcharts
  566. * @apioption series.sankey.nodes.name
  567. */
  568. /**
  569. * The vertical offset of a node in terms of weight. Positive values shift the
  570. * node downwards, negative shift it upwards.
  571. *
  572. * @type {Number}
  573. * @default 0
  574. * @sample highcharts/plotoptions/sankey-node-column/
  575. * Specified node offset
  576. * @product highcharts
  577. * @since 6.0.5
  578. * @apioption series.sankey.nodes.offset
  579. */
  580. /**
  581. * An array of data points for the series. For the `sankey` series type,
  582. * points can be given in the following way:
  583. *
  584. * An array of objects with named values. The objects are point
  585. * configuration objects as seen below. If the total number of data
  586. * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
  587. * this option is not available.
  588. *
  589. * ```js
  590. * data: [{
  591. * from: 'Category1',
  592. * to: 'Category2',
  593. * weight: 2
  594. * }, {
  595. * from: 'Category1',
  596. * to: 'Category3',
  597. * weight: 5
  598. * }]
  599. * ```
  600. *
  601. * @type {Array<Object|Array|Number>}
  602. * @extends series.line.data
  603. * @excluding drilldown,marker,x,y
  604. * @sample {highcharts} highcharts/chart/reflow-true/
  605. * Numerical values
  606. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  607. * Arrays of numeric x and y
  608. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  609. * Arrays of datetime x and y
  610. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  611. * Arrays of point.name and y
  612. * @sample {highcharts} highcharts/series/data-array-of-objects/
  613. * Config objects
  614. * @product highcharts
  615. * @apioption series.sankey.data
  616. */
  617. /**
  618. * The color for the individual _link_. By default, the link color is the same
  619. * as the node it extends from. The `series.fillOpacity` option also applies to
  620. * the points, so when setting a specific link color, consider setting the
  621. * `fillOpacity` to 1.
  622. *
  623. * @type {String}
  624. * @product highcharts
  625. * @apioption series.sankey.data.color
  626. */
  627. /**
  628. * The node that the link runs from.
  629. *
  630. * @type {String}
  631. * @product highcharts
  632. * @apioption series.sankey.data.from
  633. */
  634. /**
  635. * The node that the link runs to.
  636. *
  637. * @type {String}
  638. * @product highcharts
  639. * @apioption series.sankey.data.to
  640. */
  641. /**
  642. * Whether the link goes out of the system.
  643. *
  644. * @type {Boolean}
  645. * @default false
  646. * @sample highcharts/plotoptions/sankey-outgoing
  647. * Sankey chart with outgoing links
  648. * @product highcharts
  649. * @apioption series.sankey.data.outgoing
  650. */
  651. /**
  652. * The weight of the link.
  653. *
  654. * @type {Number}
  655. * @product highcharts
  656. * @apioption series.sankey.data.weight
  657. */
  658. }(Highcharts));
  659. }));