| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300 | /* * * Highcharts Drilldown module * * Author: Torstein Honsi * License: www.highcharts.com/license * *//** * Gets fired when a drilldown point is clicked, before the new series is added. * Note that when clicking a category label to trigger multiple series * drilldown, one `drilldown` event is triggered per point in the category. * * @callback Highcharts.DrilldownCallbackFunction * * @param {Highcharts.Chart} this *        The chart where the event occurs. * * @param {Highcharts.DrilldownEventObject} e *        The drilldown event. *//** * The event arguments when a drilldown point is clicked. * * @interface Highcharts.DrilldownEventObject *//** * If a category label was clicked, which index. * @name Highcharts.DrilldownEventObject#category * @type {number|undefined} *//** * The original browser event (usually click) that triggered the drilldown. * @name Highcharts.DrilldownEventObject#originalEvent * @type {global.Event|undefined} *//** * Prevents the default behaviour of the event. * @name Highcharts.DrilldownEventObject#preventDefault * @type {Function} *//** * The originating point. * @name Highcharts.DrilldownEventObject#point * @type {Highcharts.Point} *//** * If a category label was clicked, this array holds all points corresponing to * the category. Otherwise it is set to false. * @name Highcharts.DrilldownEventObject#points * @type {boolean|Array<Highcharts.Point>|undefined} *//** * Options for the new series. If the event is utilized for async drilldown, the * seriesOptions are not added, but rather loaded async. * @name Highcharts.DrilldownEventObject#seriesOptions * @type {Highcharts.SeriesOptionsType|undefined} *//** * The event target. * @name Highcharts.DrilldownEventObject#target * @type {Highcharts.Chart} *//** * The event type. * @name Highcharts.DrilldownEventObject#type * @type {"drilldown"} *//** * This gets fired after all the series have been drilled up. This is especially * usefull in a chart with multiple drilldown series. * * @callback Highcharts.DrillupAllCallbackFunction * * @param {Highcharts.Chart} this *        The chart where the event occurs. * * @param {Highcharts.DrillupAllEventObject} e *        The final drillup event. *//** * The event arguments when all the series have been drilled up. * * @interface Highcharts.DrillupAllEventObject *//** * Prevents the default behaviour of the event. * @name Highcharts.DrillupAllEventObject#preventDefault * @type {Function} *//** * The event target. * @name Highcharts.DrillupAllEventObject#target * @type {Highcharts.Chart} *//** * The event type. * @name Highcharts.DrillupAllEventObject#type * @type {"drillupall"} *//** * Gets fired when drilling up from a drilldown series. * * @callback Highcharts.DrillupCallbackFunction * * @param {Highcharts.Chart} this *        The chart where the event occurs. * * @param {Highcharts.DrillupEventObject} e *        The drillup event. *//** * The event arguments when drilling up from a drilldown series. * * @interface Highcharts.DrillupEventObject *//** * Prevents the default behaviour of the event. * @name Highcharts.DrillupEventObject#preventDefault * @type {Function} *//** * Options for the new series. * @name Highcharts.DrillupEventObject#seriesOptions * @type {Highcharts.SeriesOptionsType|undefined} *//** * The event target. * @name Highcharts.DrillupEventObject#target * @type {Highcharts.Chart} *//** * The event type. * @name Highcharts.DrillupEventObject#type * @type {"drillup"} */'use strict';import H from '../parts/Globals.js';import '../parts/Utilities.js';import '../parts/Options.js';import '../parts/Chart.js';import '../parts/Series.js';import '../parts/ColumnSeries.js';import '../parts/Tick.js';var animObject = H.animObject,    noop = H.noop,    color = H.color,    defaultOptions = H.defaultOptions,    extend = H.extend,    format = H.format,    objectEach = H.objectEach,    pick = H.pick,    Chart = H.Chart,    seriesTypes = H.seriesTypes,    PieSeries = seriesTypes.pie,    ColumnSeries = seriesTypes.column,    Tick = H.Tick,    fireEvent = H.fireEvent,    ddSeriesId = 1;// Add languageextend(    defaultOptions.lang,    /**     * @optionparent lang     */    {        /**         * The text for the button that appears when drilling down, linking back         * to the parent series. The parent series' name is inserted for         * `{series.name}`.         *         * @since   3.0.8         * @product highcharts highmaps         */        drillUpText: '◁ Back to {series.name}'    });/** * Options for drill down, the concept of inspecting increasingly high * resolution data through clicking on chart items like columns or pie slices. * * The drilldown feature requires the drilldown.js file to be loaded, * found in the modules directory of the download package, or online at * [code.highcharts.com/modules/drilldown.js * ](code.highcharts.com/modules/drilldown.js). * * @product      highcharts highstock highmaps * @optionparent drilldown */defaultOptions.drilldown = {    /**     * When this option is false, clicking a single point will drill down     * all points in the same category, equivalent to clicking the X axis     * label.     *     * @sample {highcharts} highcharts/drilldown/allowpointdrilldown-false/     *         Don't allow point drilldown     *     * @type      {boolean}     * @default   true     * @since     4.1.7     * @product   highcharts     * @apioption drilldown.allowPointDrilldown     */    /**     * An array of series configurations for the drill down. Each series     * configuration uses the same syntax as the [series](#series) option set.     * These drilldown series are hidden by default. The drilldown series is     * linked to the parent series' point by its `id`.     *     * @type      {Array<Highcharts.SeriesOptionsType>}     * @since     3.0.8     * @product   highcharts highmaps     * @apioption drilldown.series     */    /**     * Additional styles to apply to the X axis label for a point that     * has drilldown data. By default it is underlined and blue to invite     * to interaction.     *     * In styled mode, active label styles can be set with the     * `.highcharts-drilldown-axis-label` class.     *     * @sample {highcharts} highcharts/drilldown/labels/     *         Label styles     *     * @type    {Highcharts.CSSObject}     * @default { "cursor": "pointer", "color": "#003399", "fontWeight": "bold", "textDecoration": "underline" }     * @since   3.0.8     * @product highcharts highmaps     */    activeAxisLabelStyle: {        /** @ignore-option */        cursor: 'pointer',        /** @ignore-option */        color: '#003399',        /** @ignore-option */        fontWeight: 'bold',        /** @ignore-option */        textDecoration: 'underline'    },    /**     * Additional styles to apply to the data label of a point that has     * drilldown data. By default it is underlined and blue to invite to     * interaction.     *     * In styled mode, active data label styles can be applied with the     * `.highcharts-drilldown-data-label` class.     *     * @sample {highcharts} highcharts/drilldown/labels/     *         Label styles     *     * @type    {Highcharts.CSSObject}     * @default { "cursor": "pointer", "color": "#003399", "fontWeight": "bold", "textDecoration": "underline" }     * @since   3.0.8     * @product highcharts highmaps     */    activeDataLabelStyle: {        cursor: 'pointer',        color: '#003399',        fontWeight: 'bold',        textDecoration: 'underline'    },    /**     * Set the animation for all drilldown animations. Animation of a drilldown     * occurs when drilling between a column point and a column series,     * or a pie slice and a full pie series. Drilldown can still be used     * between series and points of different types, but animation will     * not occur.     *     * The animation can either be set as a boolean or a configuration     * object. If `true`, it will use the 'swing' jQuery easing and a duration     * of 500 ms. If used as a configuration object, the following properties     * are supported:     *     * - `duration`: The duration of the animation in milliseconds.     *     * - `easing`: A string reference to an easing function set on the `Math`     *   object. See     *   [the easing demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-animation-easing/).     *     * @type    {boolean|Highcharts.AnimationOptionsObject}     * @default { "duration": 500 }     * @since   3.0.8     * @product highcharts highmaps     */    animation: {        /** @ignore-option */        duration: 500    },    /**     * Options for the drill up button that appears when drilling down on a     * series. The text for the button is defined in     * [lang.drillUpText](#lang.drillUpText).     *     * @sample {highcharts} highcharts/drilldown/drillupbutton/     *         Drill up button     * @sample {highmaps} highcharts/drilldown/drillupbutton/     *         Drill up button     *     * @since   3.0.8     * @product highcharts highmaps     */    drillUpButton: {        /**         * What box to align the button to. Can be either `plotBox` or         * `spacingBox`.         *         * @type       {string}         * @default    plotBox         * @since      3.0.8         * @product    highcharts highmaps         * @validvalue ["plotBox", "spacingBox"]         * @apioption  drilldown.drillUpButton.relativeTo         */        /**         * A collection of attributes for the button. The object takes SVG         * attributes like `fill`, `stroke`, `stroke-width` or `r`, the border         * radius. The theme also supports `style`, a collection of CSS         * properties for the text. Equivalent attributes for the hover state         * are given in `theme.states.hover`.         *         * In styled mode, drill-up button styles can be applied with the         * `.highcharts-drillup-button` class.         *         * @sample {highcharts} highcharts/drilldown/drillupbutton/         *         Button theming         * @sample {highmaps} highcharts/drilldown/drillupbutton/         *         Button theming         *         * @type      {object}         * @since     3.0.8         * @product   highcharts highmaps         * @apioption drilldown.drillUpButton.theme         */        /**         * Positioning options for the button within the `relativeTo` box.         * Available properties are `x`, `y`, `align` and `verticalAlign`.         *         * @type    {Highcharts.AlignObject}         * @since   3.0.8         * @product highcharts highmaps         */        position: {            /**             * Vertical alignment of the button.             *             * @type      {Highcharts.VerticalAlignType}             * @default   top             * @product   highcharts highmaps             * @apioption drilldown.drillUpButton.position.verticalAlign             */            /**             * Horizontal alignment.             *             * @type {Highcharts.AlignType}             */            align: 'right',            /**             * The X offset of the button.             */            x: -10,            /**             * The Y offset of the button.             */            y: 10        }    }};/** * Fires when a drilldown point is clicked, before the new series is added. This * event is also utilized for async drilldown, where the seriesOptions are not * added by option, but rather loaded async. Note that when clicking a category * label to trigger multiple series drilldown, one `drilldown` event is * triggered per point in the category. * * Event arguments: * * - `category`: If a category label was clicked, which index.</dd> * * - `originalEvent`: The original browser event (usually click) that triggered *   the drilldown. * * - `point`: The originating point. * * - `points`: If a category label was clicked, this array holds all points *   corresponing to the category.</dd> * * - `seriesOptions`: Options for the new series. * * @sample {highcharts} highcharts/drilldown/async/ *         Async drilldown * * @type      {Highcharts.DrilldownCallbackFunction} * @since     3.0.8 * @product   highcharts highmaps * @context   Highcharts.Chart * @apioption chart.events.drilldown *//** * Fires when drilling up from a drilldown series. * * @type      {Highcharts.DrillupCallbackFunction} * @since     3.0.8 * @product   highcharts highmaps * @context   Highcharts.Chart * @apioption chart.events.drillup *//** * In a chart with multiple drilldown series, this event fires after all the * series have been drilled up. * * @type      {Highcharts.DrillupAllCallbackFunction} * @since     4.2.4 * @product   highcharts highmaps * @context   Highcharts.Chart * @apioption chart.events.drillupall *//** * The `id` of a series in the [drilldown.series](#drilldown.series) array to * use for a drilldown for this point. * * @sample {highcharts} highcharts/drilldown/basic/ *         Basic drilldown * * @type      {string} * @since     3.0.8 * @product   highcharts * @apioption series.line.data.drilldown *//** * A general fadeIn method. * * @requires module:modules/drilldown * * @function Highcharts.SVGElement#fadeIn * * @param {Highcharts.AnimationOptionsObject} [animation] */H.SVGRenderer.prototype.Element.prototype.fadeIn = function (animation) {    this        .attr({            opacity: 0.1,            visibility: 'inherit'        })        .animate({            opacity: pick(this.newOpacity, 1) // newOpacity used in maps        }, animation || {            duration: 250        });};/** * Add a series to the chart as drilldown from a specific point in the parent * series. This method is used for async drilldown, when clicking a point in a * series should result in loading and displaying a more high-resolution series. * When not async, the setup is simpler using the * [drilldown.series](https://api.highcharts.com/highcharts/drilldown.series) * options structure. * * @sample highcharts/drilldown/async/ *         Async drilldown * * @function Highcharts.Chart#addSeriesAsDrilldown * * @param {Highcharts.Point} point *        The point from which the drilldown will start. * * @param {Highcharts.SeriesOptionsType} options *        The series options for the new, detailed series. */Chart.prototype.addSeriesAsDrilldown = function (point, options) {    this.addSingleSeriesAsDrilldown(point, options);    this.applyDrilldown();};Chart.prototype.addSingleSeriesAsDrilldown = function (point, ddOptions) {    var oldSeries = point.series,        xAxis = oldSeries.xAxis,        yAxis = oldSeries.yAxis,        newSeries,        pointIndex,        levelSeries = [],        levelSeriesOptions = [],        level,        levelNumber,        last,        colorProp;    colorProp = this.styledMode ?        { colorIndex: pick(point.colorIndex, oldSeries.colorIndex) } :        { color: point.color || oldSeries.color };    if (!this.drilldownLevels) {        this.drilldownLevels = [];    }    levelNumber = oldSeries.options._levelNumber || 0;    // See if we can reuse the registered series from last run    last = this.drilldownLevels[this.drilldownLevels.length - 1];    if (last && last.levelNumber !== levelNumber) {        last = undefined;    }    ddOptions = extend(extend({        _ddSeriesId: ddSeriesId++    }, colorProp), ddOptions);    pointIndex = oldSeries.points.indexOf(point);    // Record options for all current series    oldSeries.chart.series.forEach(function (series) {        if (series.xAxis === xAxis && !series.isDrilling) {            series.options._ddSeriesId =                series.options._ddSeriesId || ddSeriesId++;            series.options._colorIndex = series.userOptions._colorIndex;            series.options._levelNumber =                series.options._levelNumber || levelNumber; // #3182            if (last) {                levelSeries = last.levelSeries;                levelSeriesOptions = last.levelSeriesOptions;            } else {                levelSeries.push(series);                levelSeriesOptions.push(series.options);            }        }    });    // Add a record of properties for each drilldown level    level = extend({        levelNumber: levelNumber,        seriesOptions: oldSeries.options,        levelSeriesOptions: levelSeriesOptions,        levelSeries: levelSeries,        shapeArgs: point.shapeArgs,        // no graphic in line series with markers disabled        bBox: point.graphic ? point.graphic.getBBox() : {},        color: point.isNull ? new H.Color(color).setOpacity(0).get() : color,        lowerSeriesOptions: ddOptions,        pointOptions: oldSeries.options.data[pointIndex],        pointIndex: pointIndex,        oldExtremes: {            xMin: xAxis && xAxis.userMin,            xMax: xAxis && xAxis.userMax,            yMin: yAxis && yAxis.userMin,            yMax: yAxis && yAxis.userMax        },        resetZoomButton: this.resetZoomButton    }, colorProp);    // Push it to the lookup array    this.drilldownLevels.push(level);    // Reset names to prevent extending (#6704)    if (xAxis && xAxis.names) {        xAxis.names.length = 0;    }    newSeries = level.lowerSeries = this.addSeries(ddOptions, false);    newSeries.options._levelNumber = levelNumber + 1;    if (xAxis) {        xAxis.oldPos = xAxis.pos;        xAxis.userMin = xAxis.userMax = null;        yAxis.userMin = yAxis.userMax = null;    }    // Run fancy cross-animation on supported and equal types    if (oldSeries.type === newSeries.type) {        newSeries.animate = newSeries.animateDrilldown || noop;        newSeries.options.animation = true;    }};Chart.prototype.applyDrilldown = function () {    var drilldownLevels = this.drilldownLevels,        levelToRemove;    if (drilldownLevels && drilldownLevels.length > 0) { // #3352, async loading        levelToRemove = drilldownLevels[drilldownLevels.length - 1].levelNumber;        this.drilldownLevels.forEach(function (level) {            if (level.levelNumber === levelToRemove) {                level.levelSeries.forEach(function (series) {                    // Not removed, not added as part of a multi-series                    // drilldown                    if (                        series.options &&                        series.options._levelNumber === levelToRemove                    ) {                        series.remove(false);                    }                });            }        });    }    // We have a reset zoom button. Hide it and detatch it from the chart. It    // is preserved to the layer config above.    if (this.resetZoomButton) {        this.resetZoomButton.hide();        delete this.resetZoomButton;    }    this.pointer.reset();    this.redraw();    this.showDrillUpButton();};Chart.prototype.getDrilldownBackText = function () {    var drilldownLevels = this.drilldownLevels,        lastLevel;    if (drilldownLevels && drilldownLevels.length > 0) { // #3352, async loading        lastLevel = drilldownLevels[drilldownLevels.length - 1];        lastLevel.series = lastLevel.seriesOptions;        return format(this.options.lang.drillUpText, lastLevel);    }};Chart.prototype.showDrillUpButton = function () {    var chart = this,        backText = this.getDrilldownBackText(),        buttonOptions = chart.options.drilldown.drillUpButton,        attr,        states;    if (!this.drillUpButton) {        attr = buttonOptions.theme;        states = attr && attr.states;        this.drillUpButton = this.renderer.button(            backText,            null,            null,            function () {                chart.drillUp();            },            attr,            states && states.hover,            states && states.select        )            .addClass('highcharts-drillup-button')            .attr({                align: buttonOptions.position.align,                zIndex: 7            })            .add()            .align(                buttonOptions.position,                false,                buttonOptions.relativeTo || 'plotBox'            );    } else {        this.drillUpButton.attr({            text: backText        })            .align();    }};/** * When the chart is drilled down to a child series, calling `chart.drillUp()` * will drill up to the parent series. Requires the drilldown module. * * @function Highcharts.Chart#drillUp */Chart.prototype.drillUp = function () {    if (!this.drilldownLevels || this.drilldownLevels.length === 0) {        return;    }    var chart = this,        drilldownLevels = chart.drilldownLevels,        levelNumber = drilldownLevels[drilldownLevels.length - 1].levelNumber,        i = drilldownLevels.length,        chartSeries = chart.series,        seriesI,        level,        oldSeries,        newSeries,        oldExtremes,        addSeries = function (seriesOptions) {            var addedSeries;            chartSeries.forEach(function (series) {                if (series.options._ddSeriesId === seriesOptions._ddSeriesId) {                    addedSeries = series;                }            });            addedSeries = addedSeries || chart.addSeries(seriesOptions, false);            if (                addedSeries.type === oldSeries.type &&                addedSeries.animateDrillupTo            ) {                addedSeries.animate = addedSeries.animateDrillupTo;            }            if (seriesOptions === level.seriesOptions) {                newSeries = addedSeries;            }        };    while (i--) {        level = drilldownLevels[i];        if (level.levelNumber === levelNumber) {            drilldownLevels.pop();            // Get the lower series by reference or id            oldSeries = level.lowerSeries;            if (!oldSeries.chart) { // #2786                seriesI = chartSeries.length; // #2919                while (seriesI--) {                    if (                        chartSeries[seriesI].options.id ===                            level.lowerSeriesOptions.id &&                        chartSeries[seriesI].options._levelNumber ===                            levelNumber + 1                    ) { // #3867                        oldSeries = chartSeries[seriesI];                        break;                    }                }            }            oldSeries.xData = []; // Overcome problems with minRange (#2898)            level.levelSeriesOptions.forEach(addSeries);            fireEvent(chart, 'drillup', { seriesOptions: level.seriesOptions });            if (newSeries.type === oldSeries.type) {                newSeries.drilldownLevel = level;                newSeries.options.animation = chart.options.drilldown.animation;                if (oldSeries.animateDrillupFrom && oldSeries.chart) { // #2919                    oldSeries.animateDrillupFrom(level);                }            }            newSeries.options._levelNumber = levelNumber;            oldSeries.remove(false);            // Reset the zoom level of the upper series            if (newSeries.xAxis) {                oldExtremes = level.oldExtremes;                newSeries.xAxis.setExtremes(                    oldExtremes.xMin,                    oldExtremes.xMax,                    false                );                newSeries.yAxis.setExtremes(                    oldExtremes.yMin,                    oldExtremes.yMax,                    false                );            }            // We have a resetZoomButton tucked away for this level. Attatch            // it to the chart and show it.            if (level.resetZoomButton) {                chart.resetZoomButton = level.resetZoomButton;                chart.resetZoomButton.show();            }        }    }    // Fire a once-off event after all series have been drilled up (#5158)    fireEvent(chart, 'drillupall');    this.redraw();    if (this.drilldownLevels.length === 0) {        this.drillUpButton = this.drillUpButton.destroy();    } else {        this.drillUpButton.attr({            text: this.getDrilldownBackText()        })            .align();    }    this.ddDupes.length = []; // #3315};// Add update function to be called internally from Chart.update (#7600)Chart.prototype.callbacks.push(function () {    var chart = this;    chart.drilldown = {        update: function (options, redraw) {            H.merge(true, chart.options.drilldown, options);            if (pick(redraw, true)) {                chart.redraw();            }        }    };});// Don't show the reset button if we already are displaying the drillUp button.H.addEvent(Chart, 'beforeShowResetZoom', function () {    if (this.drillUpButton) {        return false;    }});H.addEvent(Chart, 'render', function setDDPoints() {    (this.xAxis || []).forEach(function (axis) {        axis.ddPoints = {};        axis.series.forEach(function (series) {            var i,                xData = series.xData || [],                points = series.points,                p;            for (i = 0; i < xData.length; i++) {                p = series.options.data[i];                // The `drilldown` property can only be set on an array or an                // object                if (typeof p !== 'number') {                    // Convert array to object (#8008)                    p = series.pointClass.prototype.optionsToObject                        .call({ series: series }, p);                    if (p.drilldown) {                        if (!axis.ddPoints[xData[i]]) {                            axis.ddPoints[xData[i]] = [];                        }                        axis.ddPoints[xData[i]].push(points ? points[i] : true);                    }                }            }        });        // Add drillability to ticks, and always keep it drillability updated        // (#3951)        objectEach(axis.ticks, Tick.prototype.drillable);    });});/** * When drilling up, keep the upper series invisible until the lower series has * moved into place. * * @private * @function Highcharts.ColumnSeries#animateDrillupTo * * @param {boolean} [init=false] */ColumnSeries.prototype.animateDrillupTo = function (init) {    if (!init) {        var newSeries = this,            level = newSeries.drilldownLevel;        // First hide all items before animating in again        this.points.forEach(function (point) {            var dataLabel = point.dataLabel;            if (point.graphic) { // #3407                point.graphic.hide();            }            if (dataLabel) {                // The data label is initially hidden, make sure it is not faded                // in (#6127)                dataLabel.hidden = dataLabel.attr('visibility') === 'hidden';                if (!dataLabel.hidden) {                    dataLabel.hide();                    if (point.connector) {                        point.connector.hide();                    }                }            }        });        // Do dummy animation on first point to get to complete        H.syncTimeout(function () {            if (newSeries.points) { // May be destroyed in the meantime, #3389                newSeries.points.forEach(function (point, i) {                    // Fade in other points                    var verb =                        i === (level && level.pointIndex) ? 'show' : 'fadeIn',                        inherit = verb === 'show' ? true : undefined,                        dataLabel = point.dataLabel;                    if (point.graphic) { // #3407                        point.graphic[verb](inherit);                    }                    if (dataLabel && !dataLabel.hidden) { // #6127                        dataLabel.fadeIn(); // #7384                        if (point.connector) {                            point.connector.fadeIn();                        }                    }                });            }        }, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));        // Reset        this.animate = noop;    }};ColumnSeries.prototype.animateDrilldown = function (init) {    var series = this,        chart = this.chart,        drilldownLevels = chart.drilldownLevels,        animateFrom,        animationOptions = animObject(chart.options.drilldown.animation),        xAxis = this.xAxis,        styledMode = chart.styledMode;    if (!init) {        drilldownLevels.forEach(function (level) {            if (                series.options._ddSeriesId ===                    level.lowerSeriesOptions._ddSeriesId            ) {                animateFrom = level.shapeArgs;                if (!styledMode) {                    // Add the point colors to animate from                    animateFrom.fill = level.color;                }            }        });        animateFrom.x += (pick(xAxis.oldPos, xAxis.pos) - xAxis.pos);        this.points.forEach(function (point) {            var animateTo = point.shapeArgs;            if (!styledMode) {                // Add the point colors to animate to                animateTo.fill = point.color;            }            if (point.graphic) {                point.graphic                    .attr(animateFrom)                    .animate(                        extend(                            point.shapeArgs,                            { fill: point.color || series.color }                        ),                        animationOptions                    );            }            if (point.dataLabel) {                point.dataLabel.fadeIn(animationOptions);            }        });        this.animate = null;    }};/** * When drilling up, pull out the individual point graphics from the lower * series and animate them into the origin point in the upper series. * * @private * @function Highcharts.ColumnSeries#animateDrillupFrom * * @param {object} level */ColumnSeries.prototype.animateDrillupFrom = function (level) {    var animationOptions = animObject(this.chart.options.drilldown.animation),        group = this.group,        // For 3d column series all columns are added to one group        // so we should not delete the whole group. #5297        removeGroup = group !== this.chart.columnGroup,        series = this;    // Cancel mouse events on the series group (#2787)    series.trackerGroups.forEach(function (key) {        if (series[key]) { // we don't always have dataLabelsGroup            series[key].on('mouseover');        }    });    if (removeGroup) {        delete this.group;    }    this.points.forEach(function (point) {        var graphic = point.graphic,            animateTo = level.shapeArgs,            complete = function () {                graphic.destroy();                if (group && removeGroup) {                    group = group.destroy();                }            };        if (graphic) {            delete point.graphic;            if (!series.chart.styledMode) {                animateTo.fill = level.color;            }            if (animationOptions.duration) {                graphic.animate(                    animateTo,                    H.merge(animationOptions, { complete: complete })                );            } else {                graphic.attr(animateTo);                complete();            }        }    });};if (PieSeries) {    extend(PieSeries.prototype, {        animateDrillupTo: ColumnSeries.prototype.animateDrillupTo,        animateDrillupFrom: ColumnSeries.prototype.animateDrillupFrom,        animateDrilldown: function (init) {            var level = this.chart.drilldownLevels[                    this.chart.drilldownLevels.length - 1                ],                animationOptions = this.chart.options.drilldown.animation,                animateFrom = level.shapeArgs,                start = animateFrom.start,                angle = animateFrom.end - start,                startAngle = angle / this.points.length,                styledMode = this.chart.styledMode;            if (!init) {                this.points.forEach(function (point, i) {                    var animateTo = point.shapeArgs;                    if (!styledMode) {                        animateFrom.fill = level.color;                        animateTo.fill = point.color;                    }                    if (point.graphic) {                        point.graphic                            .attr(H.merge(animateFrom, {                                start: start + i * startAngle,                                end: start + (i + 1) * startAngle                            }))[animationOptions ? 'animate' : 'attr'](                                animateTo,                                animationOptions                            );                    }                });                this.animate = null;            }        }    });}H.Point.prototype.doDrilldown = function (    _holdRedraw,    category,    originalEvent) {    var series = this.series,        chart = series.chart,        drilldown = chart.options.drilldown,        i = (drilldown.series || []).length,        seriesOptions;    if (!chart.ddDupes) {        chart.ddDupes = [];    }    while (i-- && !seriesOptions) {        if (            drilldown.series[i].id === this.drilldown &&            chart.ddDupes.indexOf(this.drilldown) === -1        ) {            seriesOptions = drilldown.series[i];            chart.ddDupes.push(this.drilldown);        }    }    // Fire the event. If seriesOptions is undefined, the implementer can check    // for  seriesOptions, and call addSeriesAsDrilldown async if necessary.    fireEvent(chart, 'drilldown', {        point: this,        seriesOptions: seriesOptions,        category: category,        originalEvent: originalEvent,        points: (            category !== undefined &&            this.series.xAxis.getDDPoints(category).slice(0)        )    }, function (e) {        var chart = e.point.series && e.point.series.chart,            seriesOptions = e.seriesOptions;        if (chart && seriesOptions) {            if (_holdRedraw) {                chart.addSingleSeriesAsDrilldown(e.point, seriesOptions);            } else {                chart.addSeriesAsDrilldown(e.point, seriesOptions);            }        }    });};/** * Drill down to a given category. This is the same as clicking on an axis * label. * * @private * @function Highcharts.Axis#drilldownCategory */H.Axis.prototype.drilldownCategory = function (x, e) {    objectEach(this.getDDPoints(x), function (point) {        if (            point &&            point.series &&            point.series.visible &&            point.doDrilldown        ) { // #3197            point.doDrilldown(true, x, e);        }    });    this.chart.applyDrilldown();};/** * Return drillable points for this specific X value. * * @private * @function Highcharts.Axis#getDDPoints */H.Axis.prototype.getDDPoints = function (x) {    return this.ddPoints && this.ddPoints[x];};/** * Make a tick label drillable, or remove drilling on update. * * @private * @function Highcharts.Axis#drillable */Tick.prototype.drillable = function () {    var pos = this.pos,        label = this.label,        axis = this.axis,        isDrillable = axis.coll === 'xAxis' && axis.getDDPoints,        ddPointsX = isDrillable && axis.getDDPoints(pos),        styledMode = axis.chart.styledMode;    if (isDrillable) {        if (label && ddPointsX && ddPointsX.length) {            label.drillable = true;            if (!label.basicStyles && !styledMode) {                label.basicStyles = H.merge(label.styles);            }            label                .addClass('highcharts-drilldown-axis-label')                .on('click', function (e) {                    axis.drilldownCategory(pos, e);                });            if (!styledMode) {                label.css(axis.chart.options.drilldown.activeAxisLabelStyle);            }        } else if (label && label.drillable) {            if (!styledMode) {                label.styles = {}; // reset for full overwrite of styles                label.css(label.basicStyles);            }            label.on('click', null); // #3806            label.removeClass('highcharts-drilldown-axis-label');        }    }};// On initialization of each point, identify its label and make it clickable.// Also, provide a list of points associated to that label.H.addEvent(H.Point, 'afterInit', function () {    var point = this,        series = point.series;    if (point.drilldown) {        // Add the click event to the point        H.addEvent(point, 'click', function (e) {            if (                series.xAxis &&                series.chart.options.drilldown.allowPointDrilldown === false            ) {                series.xAxis.drilldownCategory(point.x, e); // #5822, x changed            } else {                point.doDrilldown(undefined, undefined, e);            }        });    }    return point;});H.addEvent(H.Series, 'afterDrawDataLabels', function () {    var css = this.chart.options.drilldown.activeDataLabelStyle,        renderer = this.chart.renderer,        styledMode = this.chart.styledMode;    this.points.forEach(function (point) {        var dataLabelsOptions = point.options.dataLabels,            pointCSS = pick(                point.dlOptions,                dataLabelsOptions && dataLabelsOptions.style,                {}            );        if (point.drilldown && point.dataLabel) {            if (css.color === 'contrast' && !styledMode) {                pointCSS.color = renderer.getContrast(                    point.color || this.color                );            }            if (dataLabelsOptions && dataLabelsOptions.color) {                pointCSS.color = dataLabelsOptions.color;            }            point.dataLabel                .addClass('highcharts-drilldown-data-label');            if (!styledMode) {                point.dataLabel                    .css(css)                    .css(pointCSS);            }        }    }, this);});var applyCursorCSS = function (element, cursor, addClass, styledMode) {    element[addClass ? 'addClass' : 'removeClass'](        'highcharts-drilldown-point'    );    if (!styledMode) {        element.css({ cursor: cursor });    }};// Mark the trackers with a pointerH.addEvent(H.Series, 'afterDrawTracker', function () {    var styledMode = this.chart.styledMode;    this.points.forEach(function (point) {        if (point.drilldown && point.graphic) {            applyCursorCSS(point.graphic, 'pointer', true, styledMode);        }    });});H.addEvent(H.Point, 'afterSetState', function () {    var styledMode = this.series.chart.styledMode;    if (this.drilldown && this.series.halo && this.state === 'hover') {        applyCursorCSS(this.series.halo, 'pointer', true, styledMode);    } else if (this.series.halo) {        applyCursorCSS(this.series.halo, 'auto', false, styledMode);    }});
 |