organization.src.js 24 KB

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