/* * * * (c) 2010-2019 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; var _Globals = require('./Globals.js'); var _Globals2 = _interopRequireDefault(_Globals); var _Utilities = require('./Utilities.js'); var _Utilities2 = _interopRequireDefault(_Utilities); require('./Chart.js'); require('./Options.js'); require('./Legend.js'); require('./Point.js'); require('./Series.js'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var defined = _Utilities2.default.defined, isArray = _Utilities2.default.isArray, isObject = _Utilities2.default.isObject, objectEach = _Utilities2.default.objectEach; /** * @interface Highcharts.PointEventsOptionsObject */ /** * Fires when the point is selected either programmatically or following a click * on the point. One parameter, `event`, is passed to the function. Returning * `false` cancels the operation. * @name Highcharts.PointEventsOptionsObject#select * @type {Highcharts.PointSelectCallbackFunction} */ /** * Fires when the point is unselected either programmatically or following a * click on the point. One parameter, `event`, is passed to the function. * Returning `false` cancels the operation. * @name Highcharts.PointEventsOptionsObject#unselect * @type {Highcharts.PointUnselectCallbackFunction} */ /** * Information about the select/unselect event. * * @interface Highcharts.PointInteractionEventObject * @extends global.Event */ /** * @name Highcharts.PointInteractionEventObject#accumulate * @type {boolean} */ /** * Gets fired when the point is selected either programmatically or following a * click on the point. * * @callback Highcharts.PointSelectCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {Highcharts.PointInteractionEventObject} event * Event that occured. */ /** * Fires when the point is unselected either programmatically or following a * click on the point. * * @callback Highcharts.PointUnselectCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {Highcharts.PointInteractionEventObject} event * Event that occured. */ var addEvent = _Globals2.default.addEvent, Chart = _Globals2.default.Chart, createElement = _Globals2.default.createElement, css = _Globals2.default.css, defaultOptions = _Globals2.default.defaultOptions, defaultPlotOptions = _Globals2.default.defaultPlotOptions, extend = _Globals2.default.extend, fireEvent = _Globals2.default.fireEvent, hasTouch = _Globals2.default.hasTouch, Legend = _Globals2.default.Legend, merge = _Globals2.default.merge, pick = _Globals2.default.pick, Point = _Globals2.default.Point, Series = _Globals2.default.Series, seriesTypes = _Globals2.default.seriesTypes, svg = _Globals2.default.svg, TrackerMixin; /* eslint-disable valid-jsdoc */ /** * TrackerMixin for points and graphs. * * @private * @mixin Highcharts.TrackerMixin */ TrackerMixin = _Globals2.default.TrackerMixin = { /** * Draw the tracker for a point. * * @private * @function Highcharts.TrackerMixin.drawTrackerPoint * @param {Highcharts.Series} this * @return {void} * @fires Highcharts.Series#event:afterDrawTracker */ drawTrackerPoint: function drawTrackerPoint() { var series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function onMouseOver(e) { var point = pointer.getPointFromEvent(e); // undefined on graph in scatterchart if (point !== undefined) { pointer.isDirectTouch = true; point.onMouseOver(e); } }, dataLabels; // Add reference to the point series.points.forEach(function (point) { dataLabels = isArray(point.dataLabels) ? point.dataLabels : point.dataLabel ? [point.dataLabel] : []; if (point.graphic) { point.graphic.element.point = point; } dataLabels.forEach(function (dataLabel) { if (dataLabel.div) { dataLabel.div.point = point; } else { dataLabel.element.point = point; } }); }); // Add the event listeners, we need to do this only once if (!series._hasTracking) { series.trackerGroups.forEach(function (key) { if (series[key]) { // we don't always have dataLabelsGroup series[key].addClass('highcharts-tracker').on('mouseover', onMouseOver).on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }); if (hasTouch) { series[key].on('touchstart', onMouseOver); } if (!chart.styledMode && series.options.cursor) { series[key].css(css).css({ cursor: series.options.cursor }); } } }); series._hasTracking = true; } fireEvent(this, 'afterDrawTracker'); }, /** * Draw the tracker object that sits above all data labels and markers to * track mouse events on the graph or points. For the line type charts * the tracker uses the same graphPath, but with a greater stroke width * for better control. * * @private * @function Highcharts.TrackerMixin.drawTrackerGraph * @param {Highcharts.Series} this * @return {void} * @fires Highcharts.Series#event:afterDrawTracker */ drawTrackerGraph: function drawTrackerGraph() { var series = this, options = series.options, trackByArea = options.trackByArea, trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath), trackerPathLength = trackerPath.length, chart = series.chart, pointer = chart.pointer, renderer = chart.renderer, snap = chart.options.tooltip.snap, tracker = series.tracker, i, onMouseOver = function onMouseOver() { if (chart.hoverSeries !== series) { series.onMouseOver(); } }, /* * Empirical lowest possible opacities for TRACKER_FILL for an * element to stay invisible but clickable * IE6: 0.002 * IE7: 0.002 * IE8: 0.002 * IE9: 0.00000000001 (unlimited) * IE10: 0.0001 (exporting only) * FF: 0.00000000001 (unlimited) * Chrome: 0.000001 * Safari: 0.000001 * Opera: 0.00000000001 (unlimited) */ TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')'; // Extend end points. A better way would be to use round linecaps, // but those are not clickable in VML. if (trackerPathLength && !trackByArea) { i = trackerPathLength + 1; while (i--) { if (trackerPath[i] === 'M') { // extend left side trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], 'L'); } if (i && trackerPath[i] === 'M' || i === trackerPathLength) { // extend right side trackerPath.splice(i, 0, 'L', trackerPath[i - 2] + snap, trackerPath[i - 1]); } } } // draw the tracker if (tracker) { tracker.attr({ d: trackerPath }); } else if (series.graph) { // create series.tracker = renderer.path(trackerPath).attr({ visibility: series.visible ? 'visible' : 'hidden', zIndex: 2 }).addClass(trackByArea ? 'highcharts-tracker-area' : 'highcharts-tracker-line').add(series.group); if (!chart.styledMode) { series.tracker.attr({ 'stroke-linejoin': 'round', stroke: TRACKER_FILL, fill: trackByArea ? TRACKER_FILL : 'none', 'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap) }); } // The tracker is added to the series group, which is clipped, but // is covered by the marker group. So the marker group also needs to // capture events. [series.tracker, series.markerGroup].forEach(function (tracker) { tracker.addClass('highcharts-tracker').on('mouseover', onMouseOver).on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }); if (options.cursor && !chart.styledMode) { tracker.css({ cursor: options.cursor }); } if (hasTouch) { tracker.on('touchstart', onMouseOver); } }); } fireEvent(this, 'afterDrawTracker'); } }; /* End TrackerMixin */ // Add tracking event listener to the series group, so the point graphics // themselves act as trackers if (seriesTypes.column) { /** * @private * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.column#drawTracker */ seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint; } if (seriesTypes.pie) { /** * @private * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.pie#drawTracker */ seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint; } if (seriesTypes.scatter) { /** * @private * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.scatter#drawTracker */ seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint; } // Extend Legend for item events. extend(Legend.prototype, { /** * @private * @function Highcharts.Legend#setItemEvents * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item * @param {Highcharts.SVGElement} legendItem * @param {boolean} [useHTML=false] * @return {void} * @fires Highcharts.Point#event:legendItemClick * @fires Highcharts.Series#event:legendItemClick */ setItemEvents: function setItemEvents(item, legendItem, useHTML) { var legend = this, boxWrapper = legend.chart.renderer.boxWrapper, isPoint = item instanceof Point, activeClass = 'highcharts-legend-' + (isPoint ? 'point' : 'series') + '-active', styledMode = legend.chart.styledMode; // Set the events on the item group, or in case of useHTML, the item // itself (#1249) (useHTML ? legendItem : item.legendGroup).on('mouseover', function () { if (item.visible) { legend.allItems.forEach(function (inactiveItem) { if (item !== inactiveItem) { inactiveItem.setState('inactive', !isPoint); } }); } item.setState('hover'); // A CSS class to dim or hide other than the hovered series. // Works only if hovered series is visible (#10071). if (item.visible) { boxWrapper.addClass(activeClass); } if (!styledMode) { legendItem.css(legend.options.itemHoverStyle); } }).on('mouseout', function () { if (!legend.chart.styledMode) { legendItem.css(merge(item.visible ? legend.itemStyle : legend.itemHiddenStyle)); } legend.allItems.forEach(function (inactiveItem) { if (item !== inactiveItem) { inactiveItem.setState('', !isPoint); } }); // A CSS class to dim or hide other than the hovered series boxWrapper.removeClass(activeClass); item.setState(); }).on('click', function (event) { var strLegendItemClick = 'legendItemClick', fnLegendItemClick = function fnLegendItemClick() { if (item.setVisible) { item.setVisible(); } // Reset inactive state legend.allItems.forEach(function (inactiveItem) { if (item !== inactiveItem) { inactiveItem.setState(item.visible ? 'inactive' : '', !isPoint); } }); }; // A CSS class to dim or hide other than the hovered series. // Event handling in iOS causes the activeClass to be added // prior to click in some cases (#7418). boxWrapper.removeClass(activeClass); // Pass over the click/touch event. #4. event = { browserEvent: event }; // click the name or symbol if (item.firePointEvent) { // point item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); } else { fireEvent(item, strLegendItemClick, event, fnLegendItemClick); } }); }, /** * @private * @function Highcharts.Legend#createCheckboxForItem * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item * @return {void} * @fires Highcharts.Series#event:checkboxClick */ createCheckboxForItem: function createCheckboxForItem(item) { var legend = this; item.checkbox = createElement('input', { type: 'checkbox', className: 'highcharts-legend-checkbox', checked: item.selected, defaultChecked: item.selected // required by IE7 }, legend.options.itemCheckboxStyle, legend.chart.container); addEvent(item.checkbox, 'click', function (event) { var target = event.target; fireEvent(item.series || item, 'checkboxClick', { checked: target.checked, item: item }, function () { item.select(); }); }); } }); // Extend the Chart object with interaction extend(Chart.prototype, /** @lends Chart.prototype */{ /** * Display the zoom button. * * @private * @function Highcharts.Chart#showResetZoom * @return {void} * @fires Highcharts.Chart#event:beforeShowResetZoom */ showResetZoom: function showResetZoom() { var chart = this, lang = defaultOptions.lang, btnOptions = chart.options.chart.resetZoomButton, theme = btnOptions.theme, states = theme.states, alignTo = btnOptions.relativeTo === 'chart' || btnOptions.relativeTo === 'spaceBox' ? null : 'plotBox'; /** * @private */ function zoomOut() { chart.zoomOut(); } fireEvent(this, 'beforeShowResetZoom', null, function () { chart.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover).attr({ align: btnOptions.position.align, title: lang.resetZoomTitle }).addClass('highcharts-reset-zoom').add().align(btnOptions.position, false, alignTo); }); fireEvent(this, 'afterShowResetZoom'); }, /** * Zoom the chart out after a user has zoomed in. See also * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes). * * @function Highcharts.Chart#zoomOut * @return {void} * @fires Highcharts.Chart#event:selection */ zoomOut: function zoomOut() { fireEvent(this, 'selection', { resetSelection: true }, this.zoom); }, /** * Zoom into a given portion of the chart given by axis coordinates. * * @private * @function Highcharts.Chart#zoom * @param {Highcharts.SelectEventObject} event * @return {void} */ zoom: function zoom(event) { var chart = this, hasZoomed, pointer = chart.pointer, displayButton = false, mouseDownPos = chart.inverted ? pointer.mouseDownX : pointer.mouseDownY, resetZoomButton; // If zoom is called with no arguments, reset the axes if (!event || event.resetSelection) { chart.axes.forEach(function (axis) { hasZoomed = axis.zoom(); }); pointer.initiated = false; // #6804 } else { // else, zoom in on all axes event.xAxis.concat(event.yAxis).forEach(function (axisData) { var axis = axisData.axis, axisStartPos = chart.inverted ? axis.left : axis.top, axisEndPos = chart.inverted ? axisStartPos + axis.width : axisStartPos + axis.height, isXAxis = axis.isXAxis, isWithinPane = false; // Check if zoomed area is within the pane (#1289). // In case of multiple panes only one pane should be zoomed. if (!isXAxis && mouseDownPos >= axisStartPos && mouseDownPos <= axisEndPos || isXAxis || !defined(mouseDownPos)) { isWithinPane = true; } // don't zoom more than minRange if (pointer[isXAxis ? 'zoomX' : 'zoomY'] && isWithinPane) { hasZoomed = axis.zoom(axisData.min, axisData.max); if (axis.displayBtn) { displayButton = true; } } }); } // Show or hide the Reset zoom button resetZoomButton = chart.resetZoomButton; if (displayButton && !resetZoomButton) { chart.showResetZoom(); } else if (!displayButton && isObject(resetZoomButton)) { chart.resetZoomButton = resetZoomButton.destroy(); } // Redraw if (hasZoomed) { chart.redraw(pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100)); } }, /** * Pan the chart by dragging the mouse across the pane. This function is * called on mouse move, and the distance to pan is computed from chartX * compared to the first chartX position in the dragging operation. * * @private * @function Highcharts.Chart#pan * @param {Highcharts.PointerEventObject} e * @param {string} panning * @return {void} */ pan: function pan(e, panning) { var chart = this, hoverPoints = chart.hoverPoints, doRedraw; fireEvent(this, 'pan', { originalEvent: e }, function () { // remove active points for shared tooltip if (hoverPoints) { hoverPoints.forEach(function (point) { point.setState(); }); } // xy is used in maps (panning === 'xy' ? [1, 0] : [1]).forEach(function (isX) { var axis = chart[isX ? 'xAxis' : 'yAxis'][0], horiz = axis.horiz, mousePos = e[horiz ? 'chartX' : 'chartY'], mouseDown = horiz ? 'mouseDownX' : 'mouseDownY', startPos = chart[mouseDown], halfPointRange = (axis.pointRange || 0) / 2, pointRangeDirection = axis.reversed && !chart.inverted || !axis.reversed && chart.inverted ? -1 : 1, extremes = axis.getExtremes(), panMin = axis.toValue(startPos - mousePos, true) + halfPointRange * pointRangeDirection, panMax = axis.toValue(startPos + axis.len - mousePos, true) - halfPointRange * pointRangeDirection, flipped = panMax < panMin, newMin = flipped ? panMax : panMin, newMax = flipped ? panMin : panMax, paddedMin = Math.min(extremes.dataMin, halfPointRange ? extremes.min : axis.toValue(axis.toPixels(extremes.min) - axis.minPixelPadding)), paddedMax = Math.max(extremes.dataMax, halfPointRange ? extremes.max : axis.toValue(axis.toPixels(extremes.max) + axis.minPixelPadding)), spill; // If the new range spills over, either to the min or max, // adjust the new range. spill = paddedMin - newMin; if (spill > 0) { newMax += spill; newMin = paddedMin; } spill = newMax - paddedMax; if (spill > 0) { newMax = paddedMax; newMin -= spill; } // Set new extremes if they are actually new if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max) { axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); doRedraw = true; } // set new reference for next run: chart[mouseDown] = mousePos; }); if (doRedraw) { chart.redraw(false); } css(chart.container, { cursor: 'move' }); }); } }); // Extend the Point object with interaction extend(Point.prototype, /** @lends Highcharts.Point.prototype */{ /** * Toggle the selection status of a point. * * @see Highcharts.Chart#getSelectedPoints * * @sample highcharts/members/point-select/ * Select a point from a button * @sample highcharts/chart/events-selection-points/ * Select a range of points through a drag selection * @sample maps/series/data-id/ * Select a point in Highmaps * * @function Highcharts.Point#select * * @param {boolean} [selected] * When `true`, the point is selected. When `false`, the point is * unselected. When `null` or `undefined`, the selection state is * toggled. * * @param {boolean} [accumulate=false] * When `true`, the selection is added to other selected points. * When `false`, other selected points are deselected. Internally in * Highcharts, when * [allowPointSelect](http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect) * is `true`, selected points are accumulated on Control, Shift or * Cmd clicking the point. * * @return {void} * * @fires Highcharts.Point#event:select * @fires Highcharts.Point#event:unselect */ select: function select(selected, accumulate) { var point = this, series = point.series, chart = series.chart; selected = pick(selected, !point.selected); this.selectedStaging = selected; // fire the event with the default handler point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { /** * Whether the point is selected or not. * * @see Point#select * @see Chart#getSelectedPoints * * @name Highcharts.Point#selected * @type {boolean} */ point.selected = point.options.selected = selected; series.options.data[series.data.indexOf(point)] = point.options; point.setState(selected && 'select'); // unselect all other points unless Ctrl or Cmd + click if (!accumulate) { chart.getSelectedPoints().forEach(function (loopPoint) { var loopSeries = loopPoint.series; if (loopPoint.selected && loopPoint !== point) { loopPoint.selected = loopPoint.options.selected = false; loopSeries.options.data[loopSeries.data.indexOf(loopPoint)] = loopPoint.options; // Programatically selecting a point should restore // normal state, but when click happened on other // point, set inactive state to match other points loopPoint.setState(chart.hoverPoints && loopSeries.options.inactiveOtherPoints ? 'inactive' : ''); loopPoint.firePointEvent('unselect'); } }); } }); delete this.selectedStaging; }, /** * Runs on mouse over the point. Called internally from mouse and touch * events. * * @function Highcharts.Point#onMouseOver * * @param {Highcharts.PointerEventObject} e * The event arguments. * * @return {void} */ onMouseOver: function onMouseOver(e) { var point = this, series = point.series, chart = series.chart, pointer = chart.pointer; e = e ? pointer.normalize(e) : // In cases where onMouseOver is called directly without an event pointer.getChartCoordinatesFromPoint(point, chart.inverted); pointer.runPointActions(e, point); }, /** * Runs on mouse out from the point. Called internally from mouse and touch * events. * * @function Highcharts.Point#onMouseOut * @return {void} * @fires Highcharts.Point#event:mouseOut */ onMouseOut: function onMouseOut() { var point = this, chart = point.series.chart; point.firePointEvent('mouseOut'); if (!point.series.options.inactiveOtherPoints) { (chart.hoverPoints || []).forEach(function (p) { p.setState(); }); } chart.hoverPoints = chart.hoverPoint = null; }, /** * Import events from the series' and point's options. Only do it on * demand, to save processing time on hovering. * * @private * @function Highcharts.Point#importEvents * @return {void} */ importEvents: function importEvents() { if (!this.hasImportedEvents) { var point = this, options = merge(point.series.options.point, point.options), events = options.events; point.events = events; objectEach(events, function (event, eventType) { if (_Globals2.default.isFunction(event)) { addEvent(point, eventType, event); } }); this.hasImportedEvents = true; } }, /** * Set the point's state. * * @function Highcharts.Point#setState * * @param {string} [state] * The new state, can be one of `''` (an empty string), `hover`, * `select` or `inactive`. * @param {boolean} [move] * State for animation. * * @fires Highcharts.Point#event:afterSetState */ setState: function setState(state, move) { var point = this, series = point.series, previousState = point.state, stateOptions = series.options.states[state || 'normal'] || {}, markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, normalDisabled = markerOptions && markerOptions.enabled === false, markerStateOptions = markerOptions && markerOptions.states && markerOptions.states[state || 'normal'] || {}, stateDisabled = markerStateOptions.enabled === false, stateMarkerGraphic = series.stateMarkerGraphic, pointMarker = point.marker || {}, chart = series.chart, halo = series.halo, haloOptions, markerAttribs, pointAttribs, pointAttribsAnimation, hasMarkers = markerOptions && series.markerAttribs, newSymbol; state = state || ''; // empty string if ( // already has this state state === point.state && !move || // selected points don't respond to hover point.selected && state !== 'select' || // series' state options is disabled stateOptions.enabled === false || // general point marker's state options is disabled state && (stateDisabled || normalDisabled && markerStateOptions.enabled === false) || // individual point marker's state options is disabled state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false // #1610 ) { return; } point.state = state; if (hasMarkers) { markerAttribs = series.markerAttribs(point, state); } // Apply hover styles to the existing point if (point.graphic) { if (previousState) { point.graphic.removeClass('highcharts-point-' + previousState); } if (state) { point.graphic.addClass('highcharts-point-' + state); } if (!chart.styledMode) { pointAttribs = series.pointAttribs(point, state); pointAttribsAnimation = pick(chart.options.chart.animation, stateOptions.animation); // Some inactive points (e.g. slices in pie) should apply // oppacity also for it's labels if (series.options.inactiveOtherPoints) { (point.dataLabels || []).forEach(function (label) { if (label) { label.animate({ opacity: pointAttribs.opacity }, pointAttribsAnimation); } }); if (point.connector) { point.connector.animate({ opacity: pointAttribs.opacity }, pointAttribsAnimation); } } point.graphic.animate(pointAttribs, pointAttribsAnimation); } if (markerAttribs) { point.graphic.animate(markerAttribs, pick( // Turn off globally: chart.options.chart.animation, markerStateOptions.animation, markerOptions.animation)); } // Zooming in from a range with no markers to a range with markers if (stateMarkerGraphic) { stateMarkerGraphic.hide(); } } else { // if a graphic is not applied to each point in the normal state, // create a shared graphic for the hover state if (state && markerStateOptions) { newSymbol = pointMarker.symbol || series.symbol; // If the point has another symbol than the previous one, throw // away the state marker graphic and force a new one (#1459) if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) { stateMarkerGraphic = stateMarkerGraphic.destroy(); } // Add a new state marker graphic if (!stateMarkerGraphic) { if (newSymbol) { series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(newSymbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height).add(series.markerGroup); stateMarkerGraphic.currentSymbol = newSymbol; } // Move the existing graphic } else { stateMarkerGraphic[move ? 'animate' : 'attr']({ x: markerAttribs.x, y: markerAttribs.y }); } if (!chart.styledMode && stateMarkerGraphic) { stateMarkerGraphic.attr(series.pointAttribs(point, state)); } } if (stateMarkerGraphic) { stateMarkerGraphic[state && point.isInside ? 'show' : 'hide'](); // #2450 stateMarkerGraphic.element.point = point; // #4310 } } // Show me your halo haloOptions = stateOptions.halo; var markerGraphic = point.graphic || stateMarkerGraphic; var markerVisibility = markerGraphic && markerGraphic.visibility || 'inherit'; if (haloOptions && haloOptions.size && markerVisibility !== 'hidden') { if (!halo) { series.halo = halo = chart.renderer.path() // #5818, #5903, #6705 .add(markerGraphic.parentGroup); } halo.show()[move ? 'animate' : 'attr']({ d: point.haloPath(haloOptions.size) }); halo.attr({ 'class': 'highcharts-halo highcharts-color-' + pick(point.colorIndex, series.colorIndex) + (point.className ? ' ' + point.className : ''), 'visibility': markerVisibility, 'zIndex': -1 // #4929, #8276 }); halo.point = point; // #6055 if (!chart.styledMode) { halo.attr(extend({ 'fill': point.color || series.color, 'fill-opacity': haloOptions.opacity }, haloOptions.attributes)); } } else if (halo && halo.point && halo.point.haloPath) { // Animate back to 0 on the current halo point (#6055) halo.animate({ d: halo.point.haloPath(0) }, null, // Hide after unhovering. The `complete` callback runs in the // halo's context (#7681). halo.hide); } fireEvent(point, 'afterSetState'); }, /** * Get the path definition for the halo, which is usually a shadow-like * circle around the currently hovered point. * * @function Highcharts.Point#haloPath * * @param {number} size * The radius of the circular halo. * * @return {Highcharts.SVGElement} * The path definition. */ haloPath: function haloPath(size) { var series = this.series, chart = series.chart; return chart.renderer.symbols.circle(Math.floor(this.plotX) - size, this.plotY - size, size * 2, size * 2); } }); // Extend the Series object with interaction extend(Series.prototype, /** @lends Highcharts.Series.prototype */{ /** * Runs on mouse over the series graphical items. * * @function Highcharts.Series#onMouseOver * @return {void} * @fires Highcharts.Series#event:mouseOver */ onMouseOver: function onMouseOver() { var series = this, chart = series.chart, hoverSeries = chart.hoverSeries; // set normal state to previous series if (hoverSeries && hoverSeries !== series) { hoverSeries.onMouseOut(); } // trigger the event, but to save processing time, // only if defined if (series.options.events.mouseOver) { fireEvent(series, 'mouseOver'); } // hover this series.setState('hover'); /** * Contains the original hovered series. * * @name Highcharts.Chart#hoverSeries * @type {Highcharts.Series|null} */ chart.hoverSeries = series; }, /** * Runs on mouse out of the series graphical items. * * @function Highcharts.Series#onMouseOut * * @fires Highcharts.Series#event:mouseOut */ onMouseOut: function onMouseOut() { // trigger the event only if listeners exist var series = this, options = series.options, chart = series.chart, tooltip = chart.tooltip, hoverPoint = chart.hoverPoint; // #182, set to null before the mouseOut event fires chart.hoverSeries = null; // trigger mouse out on the point, which must be in this series if (hoverPoint) { hoverPoint.onMouseOut(); } // fire the mouse out event if (series && options.events.mouseOut) { fireEvent(series, 'mouseOut'); } // hide the tooltip if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) { tooltip.hide(); } // Reset all inactive states chart.series.forEach(function (s) { s.setState('', true); }); }, /** * Set the state of the series. Called internally on mouse interaction * operations, but it can also be called directly to visually * highlight a series. * * @function Highcharts.Series#setState * * @param {string} [state] * Can be either `hover` or undefined to set to normal state. * @param {boolean} [inherit] * Determines if state should be inherited by points too. */ setState: function setState(state, inherit) { var series = this, options = series.options, graph = series.graph, inactiveOtherPoints = options.inactiveOtherPoints, stateOptions = options.states, lineWidth = options.lineWidth, opacity = options.opacity, // By default a quick animation to hover/inactive, // slower to un-hover stateAnimation = pick(stateOptions[state || 'normal'] && stateOptions[state || 'normal'].animation, series.chart.options.chart.animation), attribs, i = 0; state = state || ''; if (series.state !== state) { // Toggle class names [series.group, series.markerGroup, series.dataLabelsGroup].forEach(function (group) { if (group) { // Old state if (series.state) { group.removeClass('highcharts-series-' + series.state); } // New state if (state) { group.addClass('highcharts-series-' + state); } } }); series.state = state; if (!series.chart.styledMode) { if (stateOptions[state] && stateOptions[state].enabled === false) { return; } if (state) { lineWidth = stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0); // #4035 opacity = pick(stateOptions[state].opacity, opacity); } if (graph && !graph.dashstyle) { attribs = { 'stroke-width': lineWidth }; // Animate the graph stroke-width. graph.animate(attribs, stateAnimation); while (series['zone-graph-' + i]) { series['zone-graph-' + i].attr(attribs); i = i + 1; } } // For some types (pie, networkgraph, sankey) opacity is // resolved on a point level if (!inactiveOtherPoints) { [series.group, series.markerGroup, series.dataLabelsGroup, series.labelBySeries].forEach(function (group) { if (group) { group.animate({ opacity: opacity }, stateAnimation); } }); } } } // Don't loop over points on a series that doesn't apply inactive state // to siblings markers (e.g. line, column) if (inherit && inactiveOtherPoints && series.points) { series.setAllPointsToState(state); } }, /** * Set the state for all points in the series. * * @function Highcharts.Series#setAllPointsToState * * @private * * @param {string} [state] * Can be either `hover` or undefined to set to normal state. */ setAllPointsToState: function setAllPointsToState(state) { this.points.forEach(function (point) { if (point.setState) { point.setState(state); } }); }, /** * Show or hide the series. * * @function Highcharts.Series#setVisible * * @param {boolean} [visible] * True to show the series, false to hide. If undefined, the * visibility is toggled. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the series is altered. If doing * more operations on the chart, it is a good idea to set redraw to * false and call {@link Chart#redraw|chart.redraw()} after. * * @return {void} * * @fires Highcharts.Series#event:hide * @fires Highcharts.Series#event:show */ setVisible: function setVisible(vis, redraw) { var series = this, chart = series.chart, legendItem = series.legendItem, showOrHide, ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, oldVisibility = series.visible; // if called without an argument, toggle visibility series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618 showOrHide = vis ? 'show' : 'hide'; // show or hide elements ['group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt'].forEach(function (key) { if (series[key]) { series[key][showOrHide](); } }); // hide tooltip (#1361) if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) { series.onMouseOut(); } if (legendItem) { chart.legend.colorizeItem(series, vis); } // rescale or adapt to resized chart series.isDirty = true; // in a stack, all other series are affected if (series.options.stacking) { chart.series.forEach(function (otherSeries) { if (otherSeries.options.stacking && otherSeries.visible) { otherSeries.isDirty = true; } }); } // show or hide linked series series.linkedSeries.forEach(function (otherSeries) { otherSeries.setVisible(vis, false); }); if (ignoreHiddenSeries) { chart.isDirtyBox = true; } fireEvent(series, showOrHide); if (redraw !== false) { chart.redraw(); } }, /** * Show the series if hidden. * * @sample highcharts/members/series-hide/ * Toggle visibility from a button * * @function Highcharts.Series#show * @return {void} * @fires Highcharts.Series#event:show */ show: function show() { this.setVisible(true); }, /** * Hide the series if visible. If the {@link * https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries| * chart.ignoreHiddenSeries} option is true, the chart is redrawn without * this series. * * @sample highcharts/members/series-hide/ * Toggle visibility from a button * * @function Highcharts.Series#hide * @return {void} * @fires Highcharts.Series#event:hide */ hide: function hide() { this.setVisible(false); }, /** * Select or unselect the series. This means its * {@link Highcharts.Series.selected|selected} * property is set, the checkbox in the legend is toggled and when selected, * the series is returned by the * {@link Highcharts.Chart#getSelectedSeries} * function. * * @sample highcharts/members/series-select/ * Select a series from a button * * @function Highcharts.Series#select * * @param {boolean} [selected] * True to select the series, false to unselect. If undefined, the * selection state is toggled. * * @return {void} * * @fires Highcharts.Series#event:select * @fires Highcharts.Series#event:unselect */ select: function select(selected) { var series = this; series.selected = selected = this.options.selected = selected === undefined ? !series.selected : selected; if (series.checkbox) { series.checkbox.checked = selected; } fireEvent(series, selected ? 'select' : 'unselect'); }, /** * @private * @borrows Highcharts.TrackerMixin.drawTrackerGraph as Highcharts.Series#drawTracker */ drawTracker: TrackerMixin.drawTrackerGraph });