/* * * Parallel coordinates module * * (c) 2010-2019 Pawel Fus * * License: www.highcharts.com/license */ 'use strict'; import H from '../parts/Globals.js'; import '../parts/Axis.js'; import '../parts/Chart.js'; import '../parts/Series.js'; // Extensions for parallel coordinates plot. var Axis = H.Axis, Chart = H.Chart, ChartProto = Chart.prototype, AxisProto = H.Axis.prototype; var addEvent = H.addEvent, pick = H.pick, wrap = H.wrap, merge = H.merge, erase = H.erase, splat = H.splat, extend = H.extend, defined = H.defined, arrayMin = H.arrayMin, arrayMax = H.arrayMax; var defaultXAxisOptions = { lineWidth: 0, tickLength: 0, opposite: true, type: 'category' }; /** * @optionparent chart */ var defaultParallelOptions = { /** * Flag to render charts as a parallel coordinates plot. In a parallel * coordinates plot (||-coords) by default all required yAxes are generated * and the legend is disabled. This feature requires * `modules/parallel-coordinates.js`. * * @sample {highcharts} /highcharts/demo/parallel-coordinates/ * Parallel coordinates demo * @sample {highcharts} highcharts/parallel-coordinates/polar/ * Star plot, multivariate data in a polar chart * * @since 6.0.0 * @product highcharts */ parallelCoordinates: false, /** * Common options for all yAxes rendered in a parallel coordinates plot. * This feature requires `modules/parallel-coordinates.js`. * * The default options are: *
* parallelAxes: { * lineWidth: 1, // classic mode only * gridlinesWidth: 0, // classic mode only * title: { * text: '', * reserveSpace: false * }, * labels: { * x: 0, * y: 0, * align: 'center', * reserveSpace: false * }, * offset: 0 * }* * @sample {highcharts} highcharts/parallel-coordinates/parallelaxes/ * Set the same tickAmount for all yAxes * * @extends yAxis * @since 6.0.0 * @product highcharts * @excluding alternateGridColor, breaks, id, gridLineColor, * gridLineDashStyle, gridLineWidth, minorGridLineColor, * minorGridLineDashStyle, minorGridLineWidth, plotBands, * plotLines, angle, gridLineInterpolation, maxColor, maxZoom, * minColor, scrollbar, stackLabels, stops */ parallelAxes: { lineWidth: 1, /** * Titles for yAxes are taken from * [xAxis.categories](#xAxis.categories). All options for `xAxis.labels` * applies to parallel coordinates titles. For example, to style * categories, use [xAxis.labels.style](#xAxis.labels.style). * * @excluding align, enabled, margin, offset, position3d, reserveSpace, * rotation, skew3d, style, text, useHTML, x, y */ title: { text: '', reserveSpace: false }, labels: { x: 0, y: 4, align: 'center', reserveSpace: false }, offset: 0 } }; H.setOptions({ chart: defaultParallelOptions }); // Initialize parallelCoordinates addEvent(Chart, 'init', function (e) { var options = e.args[0], defaultyAxis = splat(options.yAxis || {}), yAxisLength = defaultyAxis.length, newYAxes = []; /** * Flag used in parallel coordinates plot to check if chart has ||-coords * (parallel coords). * * @requires module:modules/parallel-coordinates * * @name Highcharts.Chart#hasParallelCoordinates * @type {boolean} */ this.hasParallelCoordinates = options.chart && options.chart.parallelCoordinates; if (this.hasParallelCoordinates) { this.setParallelInfo(options); // Push empty yAxes in case user did not define them: for (; yAxisLength <= this.parallelInfo.counter; yAxisLength++) { newYAxes.push({}); } if (!options.legend) { options.legend = {}; } if (options.legend.enabled === undefined) { options.legend.enabled = false; } merge( true, options, // Disable boost { boost: { seriesThreshold: Number.MAX_VALUE }, plotOptions: { series: { boostThreshold: Number.MAX_VALUE } } } ); options.yAxis = defaultyAxis.concat(newYAxes); options.xAxis = merge( defaultXAxisOptions, // docs splat(options.xAxis || {})[0] ); } }); // Initialize parallelCoordinates addEvent(Chart, 'update', function (e) { var options = e.options; if (options.chart) { if (defined(options.chart.parallelCoordinates)) { this.hasParallelCoordinates = options.chart.parallelCoordinates; } if (this.hasParallelCoordinates && options.chart.parallelAxes) { this.options.chart.parallelAxes = merge( this.options.chart.parallelAxes, options.chart.parallelAxes ); this.yAxis.forEach(function (axis) { axis.update({}, false); }); } } }); extend(ChartProto, /** @lends Highcharts.Chart.prototype */ { /** * Define how many parellel axes we have according to the longest dataset. * This is quite heavy - loop over all series and check series.data.length * Consider: * * - make this an option, so user needs to set this to get better * performance * * - check only first series for number of points and assume the rest is the * same * * @private * @function Highcharts.Chart#setParallelInfo * * @param {Highcharts.Options} options * User options */ setParallelInfo: function (options) { var chart = this, seriesOptions = options.series; chart.parallelInfo = { counter: 0 }; seriesOptions.forEach(function (series) { if (series.data) { chart.parallelInfo.counter = Math.max( chart.parallelInfo.counter, series.data.length - 1 ); } }); } }); // On update, keep parallelPosition. AxisProto.keepProps.push('parallelPosition'); // Update default options with predefined for a parallel coords. addEvent(Axis, 'afterSetOptions', function (e) { var axis = this, chart = axis.chart, axisPosition = ['left', 'width', 'height', 'top']; if (chart.hasParallelCoordinates) { if (chart.inverted) { axisPosition = axisPosition.reverse(); } if (axis.isXAxis) { axis.options = merge( axis.options, defaultXAxisOptions, e.userOptions ); } else { axis.options = merge( axis.options, axis.chart.options.chart.parallelAxes, e.userOptions ); axis.parallelPosition = pick( axis.parallelPosition, chart.yAxis.length ); axis.setParallelPosition(axisPosition, axis.options); } } }); /* Each axis should gather extremes from points on a particular position in series.data. Not like the default one, which gathers extremes from all series bind to this axis. Consider using series.points instead of series.yData. */ addEvent(Axis, 'getSeriesExtremes', function (e) { if (this.chart && this.chart.hasParallelCoordinates && !this.isXAxis) { var index = this.parallelPosition, currentPoints = []; this.series.forEach(function (series) { if (series.visible && defined(series.yData[index])) { // We need to use push() beacause of null points currentPoints.push(series.yData[index]); } }); this.dataMin = arrayMin(currentPoints); this.dataMax = arrayMax(currentPoints); e.preventDefault(); } }); extend(AxisProto, /** @lends Highcharts.Axis.prototype */ { /** * Set predefined left+width and top+height (inverted) for yAxes. This * method modifies options param. * * @function Highcharts.Axis#setParallelPosition * * @param {Array