organization.src.js 17 KB

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