parallel-coordinates.src.js 21 KB

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