parallel-coordinates.src.js 16 KB

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