DependencyWheelSeries.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /* *
  2. *
  3. * Dependency wheel module
  4. *
  5. * (c) 2018-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 '../Core/Globals.js';
  14. import U from '../Core/Utilities.js';
  15. var animObject = U.animObject, seriesType = U.seriesType;
  16. import '../Core/Options.js';
  17. import NodesMixin from '../Mixins/Nodes.js';
  18. var base = H.seriesTypes.sankey.prototype;
  19. /**
  20. * @private
  21. * @class
  22. * @name Highcharts.seriesTypes.dependencywheel
  23. *
  24. * @augments Highcharts.seriesTypes.sankey
  25. */
  26. seriesType('dependencywheel', 'sankey',
  27. /**
  28. * A dependency wheel chart is a type of flow diagram, where all nodes are
  29. * laid out in a circle, and the flow between the are drawn as link bands.
  30. *
  31. * @sample highcharts/demo/dependency-wheel/
  32. * Dependency wheel
  33. *
  34. * @extends plotOptions.sankey
  35. * @exclude dataSorting
  36. * @since 7.1.0
  37. * @product highcharts
  38. * @requires modules/dependencywheel
  39. * @optionparent plotOptions.dependencywheel
  40. */
  41. {
  42. /**
  43. * The center of the wheel relative to the plot area. Can be
  44. * percentages or pixel values. The default behaviour is to
  45. * center the wheel inside the plot area.
  46. *
  47. * @type {Array<number|string|null>}
  48. * @default [null, null]
  49. * @product highcharts
  50. */
  51. center: [null, null],
  52. curveFactor: 0.6,
  53. /**
  54. * The start angle of the dependency wheel, in degrees where 0 is up.
  55. */
  56. startAngle: 0
  57. }, {
  58. orderNodes: false,
  59. getCenter: H.seriesTypes.pie.prototype.getCenter,
  60. /* eslint-disable valid-jsdoc */
  61. /**
  62. * Dependency wheel has only one column, it runs along the perimeter.
  63. * @private
  64. */
  65. createNodeColumns: function () {
  66. var columns = [this.createNodeColumn()];
  67. this.nodes.forEach(function (node) {
  68. node.column = 0;
  69. columns[0].push(node);
  70. });
  71. return columns;
  72. },
  73. /**
  74. * Translate from vertical pixels to perimeter.
  75. * @private
  76. */
  77. getNodePadding: function () {
  78. return this.options.nodePadding / Math.PI;
  79. },
  80. createNode: function (id) {
  81. var node = base.createNode.call(this, id);
  82. node.index = this.nodes.length - 1;
  83. /**
  84. * Return the sum of incoming and outgoing links.
  85. * @private
  86. */
  87. node.getSum = function () {
  88. return node.linksFrom
  89. .concat(node.linksTo)
  90. .reduce(function (acc, link) {
  91. return acc + link.weight;
  92. }, 0);
  93. };
  94. /**
  95. * Get the offset in weight values of a point/link.
  96. * @private
  97. */
  98. node.offset = function (point) {
  99. var offset = 0, i, links = node.linksFrom.concat(node.linksTo), sliced;
  100. /**
  101. * @private
  102. */
  103. function otherNode(link) {
  104. if (link.fromNode === node) {
  105. return link.toNode;
  106. }
  107. return link.fromNode;
  108. }
  109. // Sort and slice the links to avoid links going out of each
  110. // node crossing each other.
  111. links.sort(function (a, b) {
  112. return otherNode(a).index - otherNode(b).index;
  113. });
  114. for (i = 0; i < links.length; i++) {
  115. if (otherNode(links[i]).index > node.index) {
  116. links = links.slice(0, i).reverse().concat(links.slice(i).reverse());
  117. sliced = true;
  118. break;
  119. }
  120. }
  121. if (!sliced) {
  122. links.reverse();
  123. }
  124. for (i = 0; i < links.length; i++) {
  125. if (links[i] === point) {
  126. return offset;
  127. }
  128. offset += links[i].weight;
  129. }
  130. };
  131. return node;
  132. },
  133. /**
  134. * @private
  135. * @todo Override the refactored sankey translateLink and translateNode
  136. * functions instead of the whole translate function.
  137. */
  138. translate: function () {
  139. var options = this.options, factor = 2 * Math.PI /
  140. (this.chart.plotHeight + this.getNodePadding()), center = this.getCenter(), startAngle = (options.startAngle - 90) * H.deg2rad;
  141. base.translate.call(this);
  142. this.nodeColumns[0].forEach(function (node) {
  143. // Don't render the nodes if sum is 0 #12453
  144. if (node.sum) {
  145. var shapeArgs = node.shapeArgs, centerX = center[0], centerY = center[1], r = center[2] / 2, innerR = r - options.nodeWidth, start = startAngle + factor * shapeArgs.y, end = startAngle +
  146. factor * (shapeArgs.y + shapeArgs.height);
  147. // Middle angle
  148. node.angle = start + (end - start) / 2;
  149. node.shapeType = 'arc';
  150. node.shapeArgs = {
  151. x: centerX,
  152. y: centerY,
  153. r: r,
  154. innerR: innerR,
  155. start: start,
  156. end: end
  157. };
  158. node.dlBox = {
  159. x: centerX + Math.cos((start + end) / 2) * (r + innerR) / 2,
  160. y: centerY + Math.sin((start + end) / 2) * (r + innerR) / 2,
  161. width: 1,
  162. height: 1
  163. };
  164. // Draw the links from this node
  165. node.linksFrom.forEach(function (point) {
  166. if (point.linkBase) {
  167. var distance;
  168. var corners = point.linkBase.map(function (top, i) {
  169. var angle = factor * top, x = Math.cos(startAngle + angle) * (innerR + 1), y = Math.sin(startAngle + angle) * (innerR + 1), curveFactor = options.curveFactor;
  170. // The distance between the from and to node
  171. // along the perimeter. This affect how curved
  172. // the link is, so that links between neighbours
  173. // don't extend too far towards the center.
  174. distance = Math.abs(point.linkBase[3 - i] * factor - angle);
  175. if (distance > Math.PI) {
  176. distance = 2 * Math.PI - distance;
  177. }
  178. distance = distance * innerR;
  179. if (distance < innerR) {
  180. curveFactor *= (distance / innerR);
  181. }
  182. return {
  183. x: centerX + x,
  184. y: centerY + y,
  185. cpX: centerX + (1 - curveFactor) * x,
  186. cpY: centerY + (1 - curveFactor) * y
  187. };
  188. });
  189. point.shapeArgs = {
  190. d: [[
  191. 'M',
  192. corners[0].x, corners[0].y
  193. ], [
  194. 'A',
  195. innerR, innerR,
  196. 0,
  197. 0,
  198. 1,
  199. corners[1].x, corners[1].y
  200. ], [
  201. 'C',
  202. corners[1].cpX, corners[1].cpY,
  203. corners[2].cpX, corners[2].cpY,
  204. corners[2].x, corners[2].y
  205. ], [
  206. 'A',
  207. innerR, innerR,
  208. 0,
  209. 0,
  210. 1,
  211. corners[3].x, corners[3].y
  212. ], [
  213. 'C',
  214. corners[3].cpX, corners[3].cpY,
  215. corners[0].cpX, corners[0].cpY,
  216. corners[0].x, corners[0].y
  217. ]]
  218. };
  219. }
  220. });
  221. }
  222. });
  223. },
  224. animate: function (init) {
  225. if (!init) {
  226. var duration = animObject(this.options.animation).duration, step = (duration / 2) / this.nodes.length;
  227. this.nodes.forEach(function (point, i) {
  228. var graphic = point.graphic;
  229. if (graphic) {
  230. graphic.attr({ opacity: 0 });
  231. setTimeout(function () {
  232. graphic.animate({ opacity: 1 }, { duration: step });
  233. }, step * i);
  234. }
  235. }, this);
  236. this.points.forEach(function (point) {
  237. var graphic = point.graphic;
  238. if (!point.isNode && graphic) {
  239. graphic.attr({ opacity: 0 })
  240. .animate({
  241. opacity: 1
  242. }, this.options.animation);
  243. }
  244. }, this);
  245. }
  246. }
  247. /* eslint-enable valid-jsdoc */
  248. },
  249. // Point class
  250. {
  251. setState: NodesMixin.setNodeState,
  252. /* eslint-disable valid-jsdoc */
  253. /**
  254. * Return a text path that the data label uses.
  255. * @private
  256. */
  257. getDataLabelPath: function (label) {
  258. var renderer = this.series.chart.renderer, shapeArgs = this.shapeArgs, upperHalf = this.angle < 0 || this.angle > Math.PI, start = shapeArgs.start, end = shapeArgs.end;
  259. if (!this.dataLabelPath) {
  260. this.dataLabelPath = renderer
  261. .arc({ open: true })
  262. // Add it inside the data label group so it gets destroyed
  263. // with the label
  264. .add(label);
  265. }
  266. this.dataLabelPath.attr({
  267. x: shapeArgs.x,
  268. y: shapeArgs.y,
  269. r: (shapeArgs.r +
  270. (this.dataLabel.options.distance || 0)),
  271. start: (upperHalf ? start : end),
  272. end: (upperHalf ? end : start),
  273. clockwise: +upperHalf
  274. });
  275. return this.dataLabelPath;
  276. },
  277. isValid: function () {
  278. // No null points here
  279. return true;
  280. }
  281. /* eslint-enable valid-jsdoc */
  282. });
  283. /**
  284. * A `dependencywheel` series. If the [type](#series.dependencywheel.type)
  285. * option is not specified, it is inherited from [chart.type](#chart.type).
  286. *
  287. * @extends series,plotOptions.dependencywheel
  288. * @exclude dataSorting
  289. * @product highcharts
  290. * @requires modules/dependencywheel
  291. * @apioption series.dependencywheel
  292. */
  293. /**
  294. * A collection of options for the individual nodes. The nodes in a dependency
  295. * diagram are auto-generated instances of `Highcharts.Point`, but options can
  296. * be applied here and linked by the `id`.
  297. *
  298. * @extends series.sankey.nodes
  299. * @type {Array<*>}
  300. * @product highcharts
  301. * @excluding offset
  302. * @apioption series.dependencywheel.nodes
  303. */
  304. /**
  305. * An array of data points for the series. For the `dependencywheel` series
  306. * type, points can be given in the following way:
  307. *
  308. * An array of objects with named values. The following snippet shows only a
  309. * few settings, see the complete options set below. If the total number of data
  310. * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
  311. * this option is not available.
  312. *
  313. * ```js
  314. * data: [{
  315. * from: 'Category1',
  316. * to: 'Category2',
  317. * weight: 2
  318. * }, {
  319. * from: 'Category1',
  320. * to: 'Category3',
  321. * weight: 5
  322. * }]
  323. * ```
  324. *
  325. * @type {Array<*>}
  326. * @extends series.sankey.data
  327. * @product highcharts
  328. * @excluding outgoing, dataLabels
  329. * @apioption series.dependencywheel.data
  330. */
  331. /**
  332. * Individual data label for each node. The options are the same as
  333. * the ones for [series.dependencywheel.dataLabels](#series.dependencywheel.dataLabels).
  334. *
  335. * @apioption series.dependencywheel.nodes.dataLabels
  336. */
  337. ''; // adds doclets above to the transpiled file