OrganizationSeries.js 21 KB

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