organization.src.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /**
  2. * @license Highcharts JS v8.0.0 (2019-12-10)
  3. * Organization chart series type
  4. *
  5. * (c) 2019-2019 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. factory['default'] = factory;
  13. module.exports = factory;
  14. } else if (typeof define === 'function' && define.amd) {
  15. define('highcharts/modules/organization', ['highcharts', 'highcharts/modules/sankey'], function (Highcharts) {
  16. factory(Highcharts);
  17. factory.Highcharts = Highcharts;
  18. return factory;
  19. });
  20. } else {
  21. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  22. }
  23. }(function (Highcharts) {
  24. var _modules = Highcharts ? Highcharts._modules : {};
  25. function _registerModule(obj, path, args, fn) {
  26. if (!obj.hasOwnProperty(path)) {
  27. obj[path] = fn.apply(null, args);
  28. }
  29. }
  30. _registerModule(_modules, 'modules/organization.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
  31. /* *
  32. *
  33. * Organization chart module
  34. *
  35. * (c) 2018-2019 Torstein Honsi
  36. *
  37. * License: www.highcharts.com/license
  38. *
  39. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  40. *
  41. * */
  42. var pick = U.pick, wrap = U.wrap;
  43. /**
  44. * Layout value for the child nodes in an organization chart. If `hanging`, this
  45. * node's children will hang below their parent, allowing a tighter packing of
  46. * nodes in the diagram.
  47. *
  48. * @typedef {"normal"|"hanging"} Highcharts.SeriesOrganizationNodesLayoutValue
  49. */
  50. var base = H.seriesTypes.sankey.prototype;
  51. /**
  52. * @private
  53. * @class
  54. * @name Highcharts.seriesTypes.organization
  55. *
  56. * @augments Highcharts.seriesTypes.sankey
  57. */
  58. H.seriesType('organization', 'sankey',
  59. /**
  60. * An organization chart is a diagram that shows the structure of an
  61. * organization and the relationships and relative ranks of its parts and
  62. * positions.
  63. *
  64. * @sample highcharts/demo/organization-chart/
  65. * Organization chart
  66. * @sample highcharts/series-organization/horizontal/
  67. * Horizontal organization chart
  68. * @sample highcharts/series-organization/borderless
  69. * Borderless design
  70. * @sample highcharts/series-organization/center-layout
  71. * Centered layout
  72. *
  73. * @extends plotOptions.sankey
  74. * @excluding allowPointSelect, curveFactor
  75. * @since 7.1.0
  76. * @product highcharts
  77. * @requires modules/organization
  78. * @optionparent plotOptions.organization
  79. */
  80. {
  81. /**
  82. * The border color of the node cards.
  83. *
  84. * @type {Highcharts.ColorString}
  85. * @private
  86. */
  87. borderColor: '#666666',
  88. /**
  89. * The border radius of the node cards.
  90. *
  91. * @private
  92. */
  93. borderRadius: 3,
  94. /**
  95. * Radius for the rounded corners of the links between nodes.
  96. *
  97. * @sample highcharts/series-organization/link-options
  98. * Square links
  99. *
  100. * @private
  101. */
  102. linkRadius: 10,
  103. borderWidth: 1,
  104. /**
  105. * @declare Highcharts.SeriesOrganizationDataLabelsOptionsObject
  106. *
  107. * @private
  108. */
  109. dataLabels: {
  110. /* eslint-disable valid-jsdoc */
  111. /**
  112. * A callback for defining the format for _nodes_ in the
  113. * organization chart. The `nodeFormat` option takes precedence
  114. * over `nodeFormatter`.
  115. *
  116. * In an organization chart, the `nodeFormatter` is a quite complex
  117. * function of the available options, striving for a good default
  118. * layout of cards with or without images. In organization chart,
  119. * the data labels come with `useHTML` set to true, meaning they
  120. * will be rendered as true HTML above the SVG.
  121. *
  122. * @sample highcharts/series-organization/datalabels-nodeformatter
  123. * Modify the default label format output
  124. *
  125. * @type {Highcharts.SeriesSankeyDataLabelsFormatterCallbackFunction}
  126. * @since 6.0.2
  127. */
  128. nodeFormatter: function () {
  129. var outerStyle = {
  130. width: '100%',
  131. height: '100%',
  132. display: 'flex',
  133. 'flex-direction': 'row',
  134. 'align-items': 'center',
  135. 'justify-content': 'center'
  136. }, imageStyle = {
  137. 'max-height': '100%',
  138. 'border-radius': '50%'
  139. }, innerStyle = {
  140. width: '100%',
  141. padding: 0,
  142. 'text-align': 'center',
  143. 'white-space': 'normal'
  144. }, nameStyle = {
  145. margin: 0
  146. }, titleStyle = {
  147. margin: 0
  148. }, descriptionStyle = {
  149. opacity: 0.75,
  150. margin: '5px'
  151. };
  152. // eslint-disable-next-line valid-jsdoc
  153. /**
  154. * @private
  155. */
  156. function styleAttr(style) {
  157. return Object.keys(style).reduce(function (str, key) {
  158. return str + key + ':' + style[key] + ';';
  159. }, 'style="') + '"';
  160. }
  161. if (this.point.image) {
  162. imageStyle['max-width'] = '30%';
  163. innerStyle.width = '70%';
  164. }
  165. // PhantomJS doesn't support flex, roll back to absolute
  166. // positioning
  167. if (this.series.chart.renderer.forExport) {
  168. outerStyle.display = 'block';
  169. innerStyle.position = 'absolute';
  170. innerStyle.left = this.point.image ? '30%' : 0;
  171. innerStyle.top = 0;
  172. }
  173. var html = '<div ' + styleAttr(outerStyle) + '>';
  174. if (this.point.image) {
  175. html += '<img src="' + this.point.image + '" ' +
  176. styleAttr(imageStyle) + '>';
  177. }
  178. html += '<div ' + styleAttr(innerStyle) + '>';
  179. if (this.point.name) {
  180. html += '<h4 ' + styleAttr(nameStyle) + '>' +
  181. this.point.name + '</h4>';
  182. }
  183. if (this.point.title) {
  184. html += '<p ' + styleAttr(titleStyle) + '>' +
  185. (this.point.title || '') + '</p>';
  186. }
  187. if (this.point.description) {
  188. html += '<p ' + styleAttr(descriptionStyle) + '>' +
  189. this.point.description + '</p>';
  190. }
  191. html += '</div>' +
  192. '</div>';
  193. return html;
  194. },
  195. /* eslint-enable valid-jsdoc */
  196. style: {
  197. /** @internal */
  198. fontWeight: 'normal',
  199. /** @internal */
  200. fontSize: '13px'
  201. },
  202. useHTML: true
  203. },
  204. /**
  205. * The indentation in pixels of hanging nodes, nodes which parent has
  206. * [layout](#series.organization.nodes.layout) set to `hanging`.
  207. *
  208. * @private
  209. */
  210. hangingIndent: 20,
  211. /**
  212. * The color of the links between nodes.
  213. *
  214. * @type {Highcharts.ColorString}
  215. * @private
  216. */
  217. linkColor: '#666666',
  218. /**
  219. * The line width of the links connecting nodes, in pixels.
  220. *
  221. * @sample highcharts/series-organization/link-options
  222. * Square links
  223. *
  224. * @private
  225. */
  226. linkLineWidth: 1,
  227. /**
  228. * In a horizontal chart, the width of the nodes in pixels. Node that
  229. * most organization charts are vertical, so the name of this option
  230. * is counterintuitive.
  231. *
  232. * @private
  233. */
  234. nodeWidth: 50,
  235. tooltip: {
  236. nodeFormat: '{point.name}<br>{point.title}<br>{point.description}'
  237. }
  238. }, {
  239. pointAttribs: function (point, state) {
  240. var series = this, attribs = base.pointAttribs.call(series, point, state), level = point.isNode ? point.level : point.fromNode.level, levelOptions = series.mapOptionsToLevel[level || 0] || {}, options = point.options, stateOptions = (levelOptions.states && levelOptions.states[state]) || {}, values = ['borderRadius', 'linkColor', 'linkLineWidth']
  241. .reduce(function (obj, key) {
  242. obj[key] = pick(stateOptions[key], options[key], levelOptions[key], series.options[key]);
  243. return obj;
  244. }, {});
  245. if (!point.isNode) {
  246. attribs.stroke = values.linkColor;
  247. attribs['stroke-width'] = values.linkLineWidth;
  248. delete attribs.fill;
  249. }
  250. else {
  251. if (values.borderRadius) {
  252. attribs.r = values.borderRadius;
  253. }
  254. }
  255. return attribs;
  256. },
  257. createNode: function (id) {
  258. var node = base.createNode
  259. .call(this, id);
  260. // All nodes in an org chart are equal width
  261. node.getSum = function () {
  262. return 1;
  263. };
  264. return node;
  265. },
  266. createNodeColumn: function () {
  267. var column = base.createNodeColumn.call(this);
  268. // Wrap the offset function so that the hanging node's children are
  269. // aligned to their parent
  270. wrap(column, 'offset', function (proceed, node, factor) {
  271. var offset = proceed.call(this, node, factor); // eslint-disable-line no-invalid-this
  272. // Modify the default output if the parent's layout is 'hanging'
  273. if (node.hangsFrom) {
  274. return {
  275. absoluteTop: node.hangsFrom.nodeY
  276. };
  277. }
  278. return offset;
  279. });
  280. return column;
  281. },
  282. translateNode: function (node, column) {
  283. base.translateNode.call(this, node, column);
  284. if (node.hangsFrom) {
  285. node.shapeArgs.height -=
  286. this.options.hangingIndent;
  287. if (!this.chart.inverted) {
  288. node.shapeArgs.y += this.options.hangingIndent;
  289. }
  290. }
  291. node.nodeHeight = this.chart.inverted ?
  292. node.shapeArgs.width :
  293. node.shapeArgs.height;
  294. },
  295. // General function to apply corner radius to a path - can be lifted to
  296. // renderer or utilities if we need it elsewhere.
  297. curvedPath: function (path, r) {
  298. var d = [], i, x, y, x1, x2, y1, y2, directionX, directionY;
  299. for (i = 0; i < path.length; i++) {
  300. x = path[i][0];
  301. y = path[i][1];
  302. // moveTo
  303. if (i === 0) {
  304. d.push('M', x, y);
  305. }
  306. else if (i === path.length - 1) {
  307. d.push('L', x, y);
  308. // curveTo
  309. }
  310. else if (r) {
  311. x1 = path[i - 1][0];
  312. y1 = path[i - 1][1];
  313. x2 = path[i + 1][0];
  314. y2 = path[i + 1][1];
  315. // Only apply to breaks
  316. if (x1 !== x2 && y1 !== y2) {
  317. directionX = x1 < x2 ? 1 : -1;
  318. directionY = y1 < y2 ? 1 : -1;
  319. d.push('L', x - directionX * Math.min(Math.abs(x - x1), r), y - directionY * Math.min(Math.abs(y - y1), r), 'C', x, y, x, y, x + directionX * Math.min(Math.abs(x - x2), r), y + directionY * Math.min(Math.abs(y - y2), r));
  320. }
  321. // lineTo
  322. }
  323. else {
  324. d.push('L', x, y);
  325. }
  326. }
  327. return d;
  328. },
  329. translateLink: function (point) {
  330. var fromNode = point.fromNode, toNode = point.toNode, crisp = Math.round(this.options.linkLineWidth) % 2 / 2, x1 = Math.floor(fromNode.shapeArgs.x +
  331. fromNode.shapeArgs.width) + crisp, y1 = Math.floor(fromNode.shapeArgs.y +
  332. fromNode.shapeArgs.height / 2) + crisp, x2 = Math.floor(toNode.shapeArgs.x) + crisp, y2 = Math.floor(toNode.shapeArgs.y +
  333. toNode.shapeArgs.height / 2) + crisp, xMiddle, hangingIndent = this.options.hangingIndent, toOffset = toNode.options.offset, percentOffset = /%$/.test(toOffset) && parseInt(toOffset, 10), inverted = this.chart.inverted;
  334. if (inverted) {
  335. x1 -= fromNode.shapeArgs.width;
  336. x2 += toNode.shapeArgs.width;
  337. }
  338. xMiddle = Math.floor(x2 +
  339. (inverted ? 1 : -1) *
  340. (this.colDistance - this.nodeWidth) / 2) + crisp;
  341. // Put the link on the side of the node when an offset is given. HR
  342. // node in the main demo.
  343. if (percentOffset &&
  344. (percentOffset >= 50 || percentOffset <= -50)) {
  345. xMiddle = x2 = Math.floor(x2 + (inverted ? -0.5 : 0.5) *
  346. toNode.shapeArgs.width) + crisp;
  347. y2 = toNode.shapeArgs.y;
  348. if (percentOffset > 0) {
  349. y2 += toNode.shapeArgs.height;
  350. }
  351. }
  352. if (toNode.hangsFrom === fromNode) {
  353. if (this.chart.inverted) {
  354. y1 = Math.floor(fromNode.shapeArgs.y +
  355. fromNode.shapeArgs.height -
  356. hangingIndent / 2) + crisp;
  357. y2 = (toNode.shapeArgs.y +
  358. toNode.shapeArgs.height);
  359. }
  360. else {
  361. y1 = Math.floor(fromNode.shapeArgs.y +
  362. hangingIndent / 2) + crisp;
  363. }
  364. xMiddle = x2 = Math.floor(toNode.shapeArgs.x +
  365. toNode.shapeArgs.width / 2) + crisp;
  366. }
  367. point.plotY = 1;
  368. point.shapeType = 'path';
  369. point.shapeArgs = {
  370. d: this.curvedPath([
  371. [x1, y1],
  372. [xMiddle, y1],
  373. [xMiddle, y2],
  374. [x2, y2]
  375. ], this.options.linkRadius)
  376. };
  377. },
  378. alignDataLabel: function (point, dataLabel, options) {
  379. // Align the data label to the point graphic
  380. if (options.useHTML) {
  381. var width = point.shapeArgs.width, height = point.shapeArgs.height, padjust = (this.options.borderWidth +
  382. 2 * this.options.dataLabels.padding);
  383. if (this.chart.inverted) {
  384. width = height;
  385. height = point.shapeArgs.width;
  386. }
  387. height -= padjust;
  388. width -= padjust;
  389. // Set the size of the surrounding div emulating `g`
  390. H.css(dataLabel.text.element.parentNode, {
  391. width: width + 'px',
  392. height: height + 'px'
  393. });
  394. // Set properties for the span emulating `text`
  395. H.css(dataLabel.text.element, {
  396. left: 0,
  397. top: 0,
  398. width: '100%',
  399. height: '100%',
  400. overflow: 'hidden'
  401. });
  402. // The getBBox function is used in `alignDataLabel` to align
  403. // inside the box
  404. dataLabel.getBBox = function () {
  405. return {
  406. width: width,
  407. height: height
  408. };
  409. };
  410. }
  411. H.seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
  412. }
  413. });
  414. /**
  415. * An `organization` series. If the [type](#series.organization.type) option is
  416. * not specified, it is inherited from [chart.type](#chart.type).
  417. *
  418. * @extends series,plotOptions.organization
  419. * @product highcharts
  420. * @requires modules/organization
  421. * @apioption series.organization
  422. */
  423. /**
  424. * @type {Highcharts.SeriesOrganizationDataLabelsOptionsObject|Array<Highcharts.SeriesOrganizationDataLabelsOptionsObject>}
  425. * @product highcharts
  426. * @apioption series.organization.data.dataLabels
  427. */
  428. /**
  429. * A collection of options for the individual nodes. The nodes in an org chart
  430. * are auto-generated instances of `Highcharts.Point`, but options can be
  431. * applied here and linked by the `id`.
  432. *
  433. * @extends series.sankey.nodes
  434. * @type {Array<*>}
  435. * @product highcharts
  436. * @apioption series.organization.nodes
  437. */
  438. /**
  439. * Individual data label for each node. The options are the same as
  440. * the ones for [series.organization.dataLabels](#series.organization.dataLabels).
  441. *
  442. * @type {Highcharts.SeriesOrganizationDataLabelsOptionsObject|Array<Highcharts.SeriesOrganizationDataLabelsOptionsObject>}
  443. *
  444. * @apioption series.organization.nodes.dataLabels
  445. */
  446. /**
  447. * The job description for the node card, will be inserted by the default
  448. * `dataLabel.nodeFormatter`.
  449. *
  450. * @sample highcharts/demo/organization-chart
  451. * Org chart with job descriptions
  452. *
  453. * @type {string}
  454. * @product highcharts
  455. * @apioption series.organization.nodes.description
  456. */
  457. /**
  458. * An image for the node card, will be inserted by the default
  459. * `dataLabel.nodeFormatter`.
  460. *
  461. * @sample highcharts/demo/organization-chart
  462. * Org chart with images
  463. *
  464. * @type {string}
  465. * @product highcharts
  466. * @apioption series.organization.nodes.image
  467. */
  468. /**
  469. * Layout for the node's children. If `hanging`, this node's children will hang
  470. * below their parent, allowing a tighter packing of nodes in the diagram.
  471. *
  472. * @sample highcharts/demo/organization-chart
  473. * Hanging layout
  474. *
  475. * @type {Highcharts.SeriesOrganizationNodesLayoutValue}
  476. * @default normal
  477. * @product highcharts
  478. * @apioption series.organization.nodes.layout
  479. */
  480. /**
  481. * The job title for the node card, will be inserted by the default
  482. * `dataLabel.nodeFormatter`.
  483. *
  484. * @sample highcharts/demo/organization-chart
  485. * Org chart with job titles
  486. *
  487. * @type {string}
  488. * @product highcharts
  489. * @apioption series.organization.nodes.title
  490. */
  491. /**
  492. * An array of data points for the series. For the `organization` series
  493. * type, points can be given in the following way:
  494. *
  495. * An array of objects with named values. The following snippet shows only a
  496. * few settings, see the complete options set below. If the total number of data
  497. * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
  498. * this option is not available.
  499. *
  500. * ```js
  501. * data: [{
  502. * from: 'Category1',
  503. * to: 'Category2',
  504. * weight: 2
  505. * }, {
  506. * from: 'Category1',
  507. * to: 'Category3',
  508. * weight: 5
  509. * }]
  510. * ```
  511. *
  512. * @type {Array<*>}
  513. * @extends series.sankey.data
  514. * @product highcharts
  515. * @apioption series.organization.data
  516. */
  517. ''; // adds doclets above to transpiled file
  518. });
  519. _registerModule(_modules, 'masters/modules/organization.src.js', [], function () {
  520. });
  521. }));