parallel-coordinates.src.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. /**
  2. * @license Highcharts JS v9.1.1 (2021-06-04)
  3. *
  4. * Support for parallel coordinates in Highcharts
  5. *
  6. * (c) 2010-2021 Pawel Fus
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/parallel-coordinates', ['highcharts'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'Extensions/ParallelCoordinates.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Series/Series.js'], _modules['Core/Utilities.js']], function (Axis, Chart, F, H, D, Series, U) {
  32. /* *
  33. *
  34. * Parallel coordinates module
  35. *
  36. * (c) 2010-2021 Pawel Fus
  37. *
  38. * License: www.highcharts.com/license
  39. *
  40. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  41. *
  42. * */
  43. var format = F.format;
  44. var setOptions = D.setOptions;
  45. var addEvent = U.addEvent,
  46. arrayMax = U.arrayMax,
  47. arrayMin = U.arrayMin,
  48. defined = U.defined,
  49. erase = U.erase,
  50. extend = U.extend,
  51. merge = U.merge,
  52. pick = U.pick,
  53. splat = U.splat,
  54. wrap = U.wrap;
  55. /* *
  56. *
  57. * Constants
  58. *
  59. * */
  60. // Extensions for parallel coordinates plot.
  61. var ChartProto = Chart.prototype;
  62. var defaultXAxisOptions = {
  63. lineWidth: 0,
  64. tickLength: 0,
  65. opposite: true,
  66. type: 'category'
  67. };
  68. /* eslint-disable valid-jsdoc */
  69. /**
  70. * @optionparent chart
  71. */
  72. var defaultParallelOptions = {
  73. /**
  74. * Flag to render charts as a parallel coordinates plot. In a parallel
  75. * coordinates plot (||-coords) by default all required yAxes are generated
  76. * and the legend is disabled. This feature requires
  77. * `modules/parallel-coordinates.js`.
  78. *
  79. * @sample {highcharts} /highcharts/demo/parallel-coordinates/
  80. * Parallel coordinates demo
  81. * @sample {highcharts} highcharts/parallel-coordinates/polar/
  82. * Star plot,
  83. multivariate data in a polar chart
  84. *
  85. * @since 6.0.0
  86. * @product highcharts
  87. * @requires modules/parallel-coordinates
  88. */
  89. parallelCoordinates: false,
  90. /**
  91. * Common options for all yAxes rendered in a parallel coordinates plot.
  92. * This feature requires `modules/parallel-coordinates.js`.
  93. *
  94. * The default options are:
  95. * ```js
  96. * parallelAxes: {
  97. * lineWidth: 1, // classic mode only
  98. * gridlinesWidth: 0, // classic mode only
  99. * title: {
  100. * text: '',
  101. * reserveSpace: false
  102. * },
  103. * labels: {
  104. * x: 0,
  105. * y: 0,
  106. * align: 'center',
  107. * reserveSpace: false
  108. * },
  109. * offset: 0
  110. * }
  111. * ```
  112. *
  113. * @sample {highcharts} highcharts/parallel-coordinates/parallelaxes/
  114. * Set the same tickAmount for all yAxes
  115. *
  116. * @extends yAxis
  117. * @since 6.0.0
  118. * @product highcharts
  119. * @excluding alternateGridColor,
  120. breaks,
  121. id,
  122. gridLineColor,
  123. * gridLineDashStyle,
  124. gridLineWidth,
  125. minorGridLineColor,
  126. * minorGridLineDashStyle,
  127. minorGridLineWidth,
  128. plotBands,
  129. * plotLines,
  130. angle,
  131. gridLineInterpolation,
  132. maxColor,
  133. maxZoom,
  134. * minColor,
  135. scrollbar,
  136. stackLabels,
  137. stops
  138. * @requires modules/parallel-coordinates
  139. */
  140. parallelAxes: {
  141. lineWidth: 1,
  142. /**
  143. * Titles for yAxes are taken from
  144. * [xAxis.categories](#xAxis.categories). All options for `xAxis.labels`
  145. * applies to parallel coordinates titles. For example,
  146. to style
  147. * categories,
  148. use [xAxis.labels.style](#xAxis.labels.style).
  149. *
  150. * @excluding align,
  151. enabled,
  152. margin,
  153. offset,
  154. position3d,
  155. reserveSpace,
  156. * rotation,
  157. skew3d,
  158. style,
  159. text,
  160. useHTML,
  161. x,
  162. y
  163. */
  164. title: {
  165. text: '',
  166. reserveSpace: false
  167. },
  168. labels: {
  169. x: 0,
  170. y: 4,
  171. align: 'center',
  172. reserveSpace: false
  173. },
  174. offset: 0
  175. }
  176. };
  177. setOptions({
  178. chart: defaultParallelOptions
  179. });
  180. /* eslint-disable no-invalid-this */
  181. // Initialize parallelCoordinates
  182. addEvent(Chart, 'init', function (e) {
  183. var options = e.args[0],
  184. defaultYAxis = splat(options.yAxis || {}),
  185. newYAxes = [];
  186. var yAxisLength = defaultYAxis.length;
  187. /**
  188. * Flag used in parallel coordinates plot to check if chart has ||-coords
  189. * (parallel coords).
  190. *
  191. * @requires module:modules/parallel-coordinates
  192. *
  193. * @name Highcharts.Chart#hasParallelCoordinates
  194. * @type {boolean}
  195. */
  196. this.hasParallelCoordinates = options.chart &&
  197. options.chart.parallelCoordinates;
  198. if (this.hasParallelCoordinates) {
  199. this.setParallelInfo(options);
  200. // Push empty yAxes in case user did not define them:
  201. for (; yAxisLength <= this.parallelInfo.counter; yAxisLength++) {
  202. newYAxes.push({});
  203. }
  204. if (!options.legend) {
  205. options.legend = {};
  206. }
  207. if (typeof options.legend.enabled === 'undefined') {
  208. options.legend.enabled = false;
  209. }
  210. merge(true, options,
  211. // Disable boost
  212. {
  213. boost: {
  214. seriesThreshold: Number.MAX_VALUE
  215. },
  216. plotOptions: {
  217. series: {
  218. boostThreshold: Number.MAX_VALUE
  219. }
  220. }
  221. });
  222. options.yAxis = defaultYAxis.concat(newYAxes);
  223. options.xAxis = merge(defaultXAxisOptions, // docs
  224. splat(options.xAxis || {})[0]);
  225. }
  226. });
  227. // Initialize parallelCoordinates
  228. addEvent(Chart, 'update', function (e) {
  229. var options = e.options;
  230. if (options.chart) {
  231. if (defined(options.chart.parallelCoordinates)) {
  232. this.hasParallelCoordinates = options.chart.parallelCoordinates;
  233. }
  234. this.options.chart.parallelAxes = merge(this.options.chart.parallelAxes, options.chart.parallelAxes);
  235. }
  236. if (this.hasParallelCoordinates) {
  237. // (#10081)
  238. if (options.series) {
  239. this.setParallelInfo(options);
  240. }
  241. this.yAxis.forEach(function (axis) {
  242. axis.update({}, false);
  243. });
  244. }
  245. });
  246. /* eslint-disable valid-jsdoc */
  247. extend(ChartProto, /** @lends Highcharts.Chart.prototype */ {
  248. /**
  249. * Define how many parellel axes we have according to the longest dataset.
  250. * This is quite heavy - loop over all series and check series.data.length
  251. * Consider:
  252. *
  253. * - make this an option, so user needs to set this to get better
  254. * performance
  255. *
  256. * - check only first series for number of points and assume the rest is the
  257. * same
  258. *
  259. * @private
  260. * @function Highcharts.Chart#setParallelInfo
  261. * @param {Highcharts.Options} options
  262. * User options
  263. * @return {void}
  264. * @requires modules/parallel-coordinates
  265. */
  266. setParallelInfo: function (options) {
  267. var chart = this,
  268. seriesOptions = options.series;
  269. chart.parallelInfo = {
  270. counter: 0
  271. };
  272. seriesOptions.forEach(function (series) {
  273. if (series.data) {
  274. chart.parallelInfo.counter = Math.max(chart.parallelInfo.counter, series.data.length - 1);
  275. }
  276. });
  277. }
  278. });
  279. // Bind each series to each yAxis. yAxis needs a reference to all series to
  280. // calculate extremes.
  281. addEvent(Series, 'bindAxes', function (e) {
  282. if (this.chart.hasParallelCoordinates) {
  283. var series_1 = this;
  284. this.chart.axes.forEach(function (axis) {
  285. series_1.insert(axis.series);
  286. axis.isDirty = true;
  287. });
  288. series_1.xAxis = this.chart.xAxis[0];
  289. series_1.yAxis = this.chart.yAxis[0];
  290. e.preventDefault();
  291. }
  292. });
  293. // Translate each point using corresponding yAxis.
  294. addEvent(Series, 'afterTranslate', function () {
  295. var series = this,
  296. chart = this.chart,
  297. points = series.points,
  298. dataLength = points && points.length,
  299. closestPointRangePx = Number.MAX_VALUE,
  300. lastPlotX,
  301. point,
  302. i;
  303. if (this.chart.hasParallelCoordinates) {
  304. for (i = 0; i < dataLength; i++) {
  305. point = points[i];
  306. if (defined(point.y)) {
  307. if (chart.polar) {
  308. point.plotX = chart.yAxis[i].angleRad || 0;
  309. }
  310. else if (chart.inverted) {
  311. point.plotX = (chart.plotHeight -
  312. chart.yAxis[i].top +
  313. chart.plotTop);
  314. }
  315. else {
  316. point.plotX = chart.yAxis[i].left - chart.plotLeft;
  317. }
  318. point.clientX = point.plotX;
  319. point.plotY = chart.yAxis[i]
  320. .translate(point.y, false, true, null, true);
  321. if (typeof lastPlotX !== 'undefined') {
  322. closestPointRangePx = Math.min(closestPointRangePx, Math.abs(point.plotX - lastPlotX));
  323. }
  324. lastPlotX = point.plotX;
  325. point.isInside = chart.isInsidePlot(point.plotX, point.plotY, { inverted: chart.inverted });
  326. }
  327. else {
  328. point.isNull = true;
  329. }
  330. }
  331. this.closestPointRangePx = closestPointRangePx;
  332. }
  333. }, { order: 1 });
  334. // On destroy, we need to remove series from each axis.series
  335. addEvent(Series, 'destroy', function () {
  336. if (this.chart.hasParallelCoordinates) {
  337. (this.chart.axes || []).forEach(function (axis) {
  338. if (axis && axis.series) {
  339. erase(axis.series, this);
  340. axis.isDirty = axis.forceRedraw = true;
  341. }
  342. }, this);
  343. }
  344. });
  345. /**
  346. * @private
  347. */
  348. function addFormattedValue(proceed) {
  349. var chart = this.series && this.series.chart,
  350. config = proceed.apply(this,
  351. Array.prototype.slice.call(arguments, 1)),
  352. formattedValue,
  353. yAxisOptions,
  354. labelFormat,
  355. yAxis;
  356. if (chart &&
  357. chart.hasParallelCoordinates &&
  358. !defined(config.formattedValue)) {
  359. yAxis = chart.yAxis[this.x];
  360. yAxisOptions = yAxis.options;
  361. labelFormat = pick(
  362. /**
  363. * Parallel coordinates only. Format that will be used for point.y
  364. * and available in [tooltip.pointFormat](#tooltip.pointFormat) as
  365. * `{point.formattedValue}`. If not set, `{point.formattedValue}`
  366. * will use other options, in this order:
  367. *
  368. * 1. [yAxis.labels.format](#yAxis.labels.format) will be used if
  369. * set
  370. *
  371. * 2. If yAxis is a category, then category name will be displayed
  372. *
  373. * 3. If yAxis is a datetime, then value will use the same format as
  374. * yAxis labels
  375. *
  376. * 4. If yAxis is linear/logarithmic type, then simple value will be
  377. * used
  378. *
  379. * @sample {highcharts}
  380. * /highcharts/parallel-coordinates/tooltipvalueformat/
  381. * Different tooltipValueFormats's
  382. *
  383. * @type {string}
  384. * @default undefined
  385. * @since 6.0.0
  386. * @product highcharts
  387. * @requires modules/parallel-coordinates
  388. * @apioption yAxis.tooltipValueFormat
  389. */
  390. yAxisOptions.tooltipValueFormat, yAxisOptions.labels.format);
  391. if (labelFormat) {
  392. formattedValue = format(labelFormat, extend(this, { value: this.y }), chart);
  393. }
  394. else if (yAxis.dateTime) {
  395. formattedValue = chart.time.dateFormat(chart.time.resolveDTLFormat(yAxisOptions.dateTimeLabelFormats[yAxis.tickPositions.info.unitName]).main, this.y);
  396. }
  397. else if (yAxisOptions.categories) {
  398. formattedValue = yAxisOptions.categories[this.y];
  399. }
  400. else {
  401. formattedValue = this.y;
  402. }
  403. config.formattedValue = config.point.formattedValue = formattedValue;
  404. }
  405. return config;
  406. }
  407. ['line', 'spline'].forEach(function (seriesName) {
  408. wrap(H.seriesTypes[seriesName].prototype.pointClass.prototype, 'getLabelConfig', addFormattedValue);
  409. });
  410. /**
  411. * Support for parallel axes.
  412. * @private
  413. * @class
  414. */
  415. var ParallelAxisAdditions = /** @class */ (function () {
  416. /* *
  417. *
  418. * Constructors
  419. *
  420. * */
  421. function ParallelAxisAdditions(axis) {
  422. this.axis = axis;
  423. }
  424. /* *
  425. *
  426. * Functions
  427. *
  428. * */
  429. /**
  430. * Set predefined left+width and top+height (inverted) for yAxes.
  431. * This method modifies options param.
  432. *
  433. * @private
  434. *
  435. * @param {Array<string>} axisPosition
  436. * ['left', 'width', 'height', 'top'] or ['top', 'height', 'width', 'left']
  437. * for an inverted chart.
  438. *
  439. * @param {Highcharts.AxisOptions} options
  440. * Axis options.
  441. */
  442. ParallelAxisAdditions.prototype.setPosition = function (axisPosition, options) {
  443. var parallel = this,
  444. axis = parallel.axis,
  445. chart = axis.chart,
  446. fraction = ((parallel.position || 0) + 0.5) / (chart.parallelInfo.counter + 1);
  447. if (chart.polar) {
  448. options.angle = 360 * fraction;
  449. }
  450. else {
  451. options[axisPosition[0]] = 100 * fraction + '%';
  452. axis[axisPosition[1]] = options[axisPosition[1]] = 0;
  453. // In case of chart.update(inverted), remove old options:
  454. axis[axisPosition[2]] = options[axisPosition[2]] = null;
  455. axis[axisPosition[3]] = options[axisPosition[3]] = null;
  456. }
  457. };
  458. return ParallelAxisAdditions;
  459. }());
  460. /**
  461. * Axis with parallel support.
  462. * @private
  463. */
  464. var ParallelAxis;
  465. (function (ParallelAxis) {
  466. /**
  467. * Adds support for parallel axes.
  468. * @private
  469. */
  470. function compose(AxisClass) {
  471. /* eslint-disable no-invalid-this */
  472. // On update, keep parallel additions.
  473. AxisClass.keepProps.push('parallel');
  474. addEvent(AxisClass, 'init', onInit);
  475. addEvent(AxisClass, 'afterSetOptions', onAfterSetOptions);
  476. addEvent(AxisClass, 'getSeriesExtremes', onGetSeriesExtremes);
  477. }
  478. ParallelAxis.compose = compose;
  479. /**
  480. * Update default options with predefined for a parallel coords.
  481. * @private
  482. */
  483. function onAfterSetOptions(e) {
  484. var axis = this,
  485. chart = axis.chart,
  486. parallelCoordinates = axis.parallelCoordinates;
  487. var axisPosition = ['left', 'width', 'height', 'top'];
  488. if (chart.hasParallelCoordinates) {
  489. if (chart.inverted) {
  490. axisPosition = axisPosition.reverse();
  491. }
  492. if (axis.isXAxis) {
  493. axis.options = merge(axis.options, defaultXAxisOptions, e.userOptions);
  494. }
  495. else {
  496. var axisIndex = chart.yAxis.indexOf(axis); // #13608
  497. axis.options = merge(axis.options,
  498. axis.chart.options.chart.parallelAxes,
  499. e.userOptions);
  500. parallelCoordinates.position = pick(parallelCoordinates.position, axisIndex >= 0 ? axisIndex : chart.yAxis.length);
  501. parallelCoordinates.setPosition(axisPosition, axis.options);
  502. }
  503. }
  504. }
  505. /**
  506. * Each axis should gather extremes from points on a particular position in
  507. * series.data. Not like the default one, which gathers extremes from all
  508. * series bind to this axis. Consider using series.points instead of
  509. * series.yData.
  510. * @private
  511. */
  512. function onGetSeriesExtremes(e) {
  513. var axis = this;
  514. var chart = axis.chart;
  515. var parallelCoordinates = axis.parallelCoordinates;
  516. if (!parallelCoordinates) {
  517. return;
  518. }
  519. if (chart && chart.hasParallelCoordinates && !axis.isXAxis) {
  520. var index_1 = parallelCoordinates.position,
  521. currentPoints_1 = [];
  522. axis.series.forEach(function (series) {
  523. if (series.visible &&
  524. defined(series.yData[index_1])) {
  525. // We need to use push() beacause of null points
  526. currentPoints_1.push(series.yData[index_1]);
  527. }
  528. });
  529. axis.dataMin = arrayMin(currentPoints_1);
  530. axis.dataMax = arrayMax(currentPoints_1);
  531. e.preventDefault();
  532. }
  533. }
  534. /**
  535. * Add parallel addition
  536. * @private
  537. */
  538. function onInit() {
  539. var axis = this;
  540. if (!axis.parallelCoordinates) {
  541. axis.parallelCoordinates = new ParallelAxisAdditions(axis);
  542. }
  543. }
  544. })(ParallelAxis || (ParallelAxis = {}));
  545. ParallelAxis.compose(Axis);
  546. return ParallelAxis;
  547. });
  548. _registerModule(_modules, 'masters/modules/parallel-coordinates.src.js', [], function () {
  549. });
  550. }));