UNPKG

highcharts

Version:
466 lines (465 loc) 17.1 kB
/* * * * Highcharts Drilldown module * * Author: Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import A from '../../Core/Animation/AnimationUtilities.js'; const { animObject } = A; import U from '../../Core/Utilities.js'; const { addEvent, extend, fireEvent, merge, pick, syncTimeout } = U; /* * * * Functions * * */ /** @private */ function applyCursorCSS(element, cursor, addClass, styledMode) { element[addClass ? 'addClass' : 'removeClass']('highcharts-drilldown-point'); if (!styledMode) { element.css({ cursor: cursor }); } } /** @private */ function columnAnimateDrilldown(init) { const series = this, chart = series.chart, drilldownLevels = chart.drilldownLevels, animationOptions = animObject((chart.options.drilldown || {}).animation), xAxis = this.xAxis, styledMode = chart.styledMode; if (!init) { let animateFrom; (drilldownLevels || []).forEach((level) => { if (series.options._ddSeriesId === level.lowerSeriesOptions._ddSeriesId) { animateFrom = level.shapeArgs; if (!styledMode && animateFrom) { // Add the point colors to animate from animateFrom.fill = level.color; } } }); animateFrom.x += pick(xAxis.oldPos, xAxis.pos) - xAxis.pos; series.points.forEach((point) => { const 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 (chart.drilldown) { chart.drilldown.fadeInGroup(this.dataLabelsGroup); } // Reset to prototype delete this.animate; } } /** * 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 {Highcharts.DrilldownLevelObject} level * Level container * @return {void} */ function columnAnimateDrillupFrom(level) { const series = this, animationOptions = animObject((series.chart.options.drilldown || {}).animation); // Cancel mouse events on the series group (#2787) (series.trackerGroups || []).forEach((key) => { // We don't always have dataLabelsGroup if (series[key]) { series[key].on('mouseover'); } }); let group = series.group; // For 3d column series all columns are added to one group // so we should not delete the whole group. #5297 const removeGroup = group !== series.chart.columnGroup; if (removeGroup) { delete series.group; } (this.points || this.data).forEach((point) => { const graphic = point.graphic, animateTo = level.shapeArgs; if (graphic && animateTo) { const complete = () => { graphic.destroy(); if (group && removeGroup) { group = group.destroy(); } }; delete point.graphic; if (!series.chart.styledMode) { animateTo.fill = level.color; } if (animationOptions.duration) { graphic.animate(animateTo, merge(animationOptions, { complete: complete })); } else { graphic.attr(animateTo); complete(); } } }); } /** * 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] * Whether to initialize animation */ function columnAnimateDrillupTo(init) { const series = this, level = series.drilldownLevel; if (!init) { // First hide all items before animating in again series.points.forEach((point) => { const 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(); dataLabel.connector?.hide(); } } }); // Do dummy animation on first point to get to complete syncTimeout(() => { if (series.points) { // May be destroyed in the meantime, #3389 // Unable to drillup with nodes, #13711 let pointsWithNodes = []; series.data.forEach((el) => { pointsWithNodes.push(el); }); if (series.nodes) { pointsWithNodes = pointsWithNodes.concat(series.nodes); } pointsWithNodes.forEach((point, i) => { // Fade in other points const verb = i === (level && level.pointIndex) ? 'show' : 'fadeIn', inherit = verb === 'show' ? true : void 0, dataLabel = point.dataLabel; if (point.graphic && // #3407 point.visible // Don't show if invisible (#18303) ) { point.graphic[verb](inherit); } if (dataLabel && !dataLabel.hidden) { // #6127 dataLabel.fadeIn(); // #7384 dataLabel.connector?.fadeIn(); } }); } }, Math.max(series.chart.options.drilldown.animation.duration - 50, 0)); // Reset to prototype delete this.animate; } } /** @private */ function compose(SeriesClass, seriesTypes) { const PointClass = SeriesClass.prototype.pointClass, pointProto = PointClass.prototype; if (!pointProto.doDrilldown) { const { column: ColumnSeriesClass, map: MapSeriesClass, pie: PieSeriesClass } = seriesTypes; addEvent(PointClass, 'afterInit', onPointAfterInit); addEvent(PointClass, 'afterSetState', onPointAfterSetState); addEvent(PointClass, 'update', onPointUpdate); pointProto.doDrilldown = pointDoDrilldown; pointProto.runDrilldown = pointRunDrilldown; addEvent(SeriesClass, 'afterDrawDataLabels', onSeriesAfterDrawDataLabels); addEvent(SeriesClass, 'afterDrawTracker', onSeriesAfterDrawTracker); if (ColumnSeriesClass) { const columnProto = ColumnSeriesClass.prototype; columnProto.animateDrilldown = columnAnimateDrilldown; columnProto.animateDrillupFrom = columnAnimateDrillupFrom; columnProto.animateDrillupTo = columnAnimateDrillupTo; } if (MapSeriesClass) { const mapProto = MapSeriesClass.prototype; mapProto.animateDrilldown = mapAnimateDrilldown; mapProto.animateDrillupFrom = mapAnimateDrillupFrom; mapProto.animateDrillupTo = mapAnimateDrillupTo; } if (PieSeriesClass) { const pieProto = PieSeriesClass.prototype; pieProto.animateDrilldown = pieAnimateDrilldown; pieProto.animateDrillupFrom = columnAnimateDrillupFrom; pieProto.animateDrillupTo = columnAnimateDrillupTo; } } } /** * Animate in the new series. * @private */ function mapAnimateDrilldown(init) { const series = this, chart = series.chart, group = series.group; if (chart && group && series.options && chart.options.drilldown && chart.options.drilldown.animation) { // Initialize the animation if (init && chart.mapView) { group.attr({ opacity: 0.01 }); chart.mapView.allowTransformAnimation = false; // Stop duplicating and overriding animations series.options.inactiveOtherPoints = true; series.options.enableMouseTracking = false; // Run the animation } else { group.animate({ opacity: 1 }, chart.options.drilldown.animation, () => { if (series.options) { series.options.inactiveOtherPoints = false; series.options.enableMouseTracking = pick((series.userOptions && series.userOptions.enableMouseTracking), true); } }); if (chart.drilldown) { chart.drilldown.fadeInGroup(this.dataLabelsGroup); } } } } /** * 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 mapAnimateDrillupFrom() { const series = this, chart = series.chart; if (chart && chart.mapView) { chart.mapView.allowTransformAnimation = false; } // Stop duplicating and overriding animations if (series.options) { series.options.inactiveOtherPoints = true; } } /** * When drilling up, keep the upper series invisible until the lower * series has moved into place. * @private */ function mapAnimateDrillupTo(init) { const series = this, chart = series.chart, group = series.group; if (chart && group) { // Initialize the animation if (init) { group.attr({ opacity: 0.01 }); // Stop duplicating and overriding animations if (series.options) { series.options.inactiveOtherPoints = true; } // Run the animation } else { group.animate({ opacity: 1 }, (chart.options.drilldown || {}).animation); if (chart.drilldown) { chart.drilldown.fadeInGroup(series.dataLabelsGroup); } } } } /** * On initialization of each point, identify its label and make it clickable. * Also, provide a list of points associated to that label. * @private */ function onPointAfterInit() { const point = this; if (point.drilldown && !point.unbindDrilldownClick) { // Add the click event to the point point.unbindDrilldownClick = addEvent(point, 'click', onPointClick); } return point; } /** @private */ function onPointAfterSetState() { const point = this, series = point.series, styledMode = series.chart.styledMode; if (point.drilldown && series.halo && point.state === 'hover') { applyCursorCSS(series.halo, 'pointer', true, styledMode); } else if (series.halo) { applyCursorCSS(series.halo, 'auto', false, styledMode); } } /** @private */ function onPointClick(e) { const point = this, series = point.series; if (series.xAxis && (series.chart.options.drilldown || {}).allowPointDrilldown === false) { // #5822, x changed series.xAxis.drilldownCategory(point.x, e); } else { point.runDrilldown(void 0, void 0, e); } } /** @private */ function onPointUpdate(e) { const point = this, options = e.options || {}; if (options.drilldown && !point.unbindDrilldownClick) { // Add the click event to the point point.unbindDrilldownClick = addEvent(point, 'click', onPointClick); } else if (!options.drilldown && options.drilldown !== void 0 && point.unbindDrilldownClick) { point.unbindDrilldownClick = point.unbindDrilldownClick(); } } /** @private */ function onSeriesAfterDrawDataLabels() { const series = this, chart = series.chart, css = chart.options.drilldown.activeDataLabelStyle, renderer = chart.renderer, styledMode = chart.styledMode; for (const point of series.points) { const 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 || series.color); } if (dataLabelsOptions && dataLabelsOptions.color) { pointCSS.color = dataLabelsOptions.color; } point.dataLabel .addClass('highcharts-drilldown-data-label'); if (!styledMode) { point.dataLabel .css(css) .css(pointCSS); } } } } /** * Mark the trackers with a pointer. * @private */ function onSeriesAfterDrawTracker() { const series = this, styledMode = series.chart.styledMode; for (const point of series.points) { if (point.drilldown && point.graphic) { applyCursorCSS(point.graphic, 'pointer', true, styledMode); } } } /** @private */ function pieAnimateDrilldown(init) { const series = this, chart = series.chart, points = series.points, level = chart.drilldownLevels[chart.drilldownLevels.length - 1], animationOptions = chart.options.drilldown.animation; if (series.is('item')) { animationOptions.duration = 0; } // Unable to drill down in the horizontal item series #13372 if (series.center) { const animateFrom = level.shapeArgs, start = animateFrom.start, angle = animateFrom.end - start, startAngle = angle / series.points.length, styledMode = chart.styledMode; if (!init) { let animateTo, point; for (let i = 0, iEnd = points.length; i < iEnd; ++i) { point = points[i]; animateTo = point.shapeArgs; if (!styledMode) { animateFrom.fill = level.color; animateTo.fill = point.color; } if (point.graphic) { point.graphic.attr(merge(animateFrom, { start: start + i * startAngle, end: start + (i + 1) * startAngle }))[animationOptions ? 'animate' : 'attr'](animateTo, animationOptions); } } if (chart.drilldown) { chart.drilldown.fadeInGroup(series.dataLabelsGroup); } // Reset to prototype delete series.animate; } } } /** * Perform drilldown on a point instance. The [drilldown](https://api.highcharts.com/highcharts/series.line.data.drilldown) * property must be set on the point options. * * To drill down multiple points in the same category, use * `Axis.drilldownCategory` instead. * * @requires modules/drilldown * * @function Highcharts.Point#doDrilldown * * @sample {highcharts} highcharts/drilldown/programmatic * Programmatic drilldown */ function pointDoDrilldown() { this.runDrilldown(); } /** @private */ function pointRunDrilldown(holdRedraw, category, originalEvent) { const point = this, series = point.series, chart = series.chart, drilldown = chart.options.drilldown || {}; let i = (drilldown.series || []).length, seriesOptions; if (!chart.ddDupes) { chart.ddDupes = []; } // Reset the color and symbol counters after every drilldown. (#19134) chart.colorCounter = chart.symbolCounter = 0; while (i-- && !seriesOptions) { if (drilldown.series && drilldown.series[i].id === point.drilldown && point.drilldown && chart.ddDupes.indexOf(point.drilldown) === -1) { seriesOptions = drilldown.series[i]; chart.ddDupes.push(point.drilldown); } } // Fire the event. If seriesOptions is undefined, the implementer can check // for seriesOptions, and call addSeriesAsDrilldown async if necessary. fireEvent(chart, 'drilldown', { point, seriesOptions: seriesOptions, category: category, originalEvent: originalEvent, points: (typeof category !== 'undefined' && series.xAxis.getDDPoints(category).slice(0)) }, (e) => { const 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); } } }); } /* * * * Default Export * * */ const DrilldownSeries = { compose }; export default DrilldownSeries;