/* * * 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