| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170 |
- /* *
- *
- * (c) 2010-2020 Torstein Honsi
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import Chart from './Chart/Chart.js';
- import H from './Globals.js';
- import Legend from './Legend.js';
- import O from './Options.js';
- var defaultOptions = O.defaultOptions;
- import Point from '../Core/Series/Point.js';
- import U from './Utilities.js';
- var addEvent = U.addEvent, createElement = U.createElement, css = U.css, defined = U.defined, extend = U.extend, fireEvent = U.fireEvent, isArray = U.isArray, isFunction = U.isFunction, isNumber = U.isNumber, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pick = U.pick;
- /**
- * @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|undefined}
- */ /**
- * 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|undefined}
- */
- /**
- * 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.
- */
- import './Series/Series.js';
- var hasTouch = H.hasTouch, Series = H.Series, seriesTypes = H.seriesTypes, svg = H.svg, TrackerMixin;
- /* eslint-disable valid-jsdoc */
- /**
- * TrackerMixin for points and graphs.
- *
- * @private
- * @mixin Highcharts.TrackerMixin
- */
- TrackerMixin = H.TrackerMixin = {
- /**
- * Draw the tracker for a point.
- *
- * @private
- * @function Highcharts.TrackerMixin.drawTrackerPoint
- * @param {Highcharts.Series} this
- * @fires Highcharts.Series#event:afterDrawTracker
- */
- drawTrackerPoint: function () {
- var series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function (e) {
- var point = pointer.getPointFromEvent(e);
- // undefined on graph in scatterchart
- if (typeof 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
- * @fires Highcharts.Series#event:afterDrawTracker
- */
- drawTrackerGraph: function () {
- 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 (e) {
- 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) + ')';
- // 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-linecap': 'round',
- '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|Point|Highcharts.Series} item
- * @param {Highcharts.SVGElement} legendItem
- * @param {boolean} [useHTML=false]
- * @fires Highcharts.Point#event:legendItemClick
- * @fires Highcharts.Series#event:legendItemClick
- */
- setItemEvents: function (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,
- // When `useHTML`, the symbol is rendered in other group, so
- // we need to apply events listeners to both places
- legendItems = useHTML ?
- [legendItem, item.legendSymbol] :
- [item.legendGroup];
- // Set the events on the item group, or in case of useHTML, the item
- // itself (#1249)
- legendItems.forEach(function (element) {
- if (element) {
- element
- .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 () {
- 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|Point|Highcharts.Series} item
- * @fires Highcharts.Series#event:checkboxClick
- */
- createCheckboxForItem: function (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, so users can reset zoom to the default view
- * settings.
- *
- * @function Highcharts.Chart#showResetZoom
- *
- * @fires Highcharts.Chart#event:afterShowResetZoom
- * @fires Highcharts.Chart#event:beforeShowResetZoom
- */
- showResetZoom: function () {
- 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
- *
- * @fires Highcharts.Chart#event:selection
- */
- zoomOut: function () {
- 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
- */
- zoom: function (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
- */
- pan: function (e, panning) {
- var chart = this, hoverPoints = chart.hoverPoints, panningOptions, chartOptions = chart.options.chart, hasMapNavigation = chart.options.mapNavigation &&
- chart.options.mapNavigation.enabled, doRedraw, type;
- if (typeof panning === 'object') {
- panningOptions = panning;
- }
- else {
- panningOptions = {
- enabled: panning,
- type: 'x'
- };
- }
- if (chartOptions && chartOptions.panning) {
- chartOptions.panning = panningOptions;
- }
- type = panningOptions.type;
- fireEvent(this, 'pan', { originalEvent: e }, function () {
- // remove active points for shared tooltip
- if (hoverPoints) {
- hoverPoints.forEach(function (point) {
- point.setState();
- });
- }
- // panning axis mapping
- var xy = [1]; // x
- if (type === 'xy') {
- xy = [1, 0];
- }
- else if (type === 'y') {
- xy = [0];
- }
- xy.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, hasVerticalPanning = axis.hasVerticalPanning(), paddedMin, paddedMax, spill, panningState = axis.panningState;
- // General calculations of panning state.
- // This is related to using vertical panning. (#11315).
- axis.series.forEach(function (series) {
- if (hasVerticalPanning &&
- !isX && (!panningState || panningState.isDirty)) {
- var processedData = series.getProcessedData(true), dataExtremes = series.getExtremes(processedData.yData, true);
- if (!panningState) {
- panningState = {
- startMin: Number.MAX_VALUE,
- startMax: -Number.MAX_VALUE
- };
- }
- if (isNumber(dataExtremes.dataMin) &&
- isNumber(dataExtremes.dataMax)) {
- panningState.startMin = Math.min(dataExtremes.dataMin, panningState.startMin);
- panningState.startMax = Math.max(dataExtremes.dataMax, panningState.startMax);
- }
- }
- });
- paddedMin = Math.min(H.pick(panningState === null || panningState === void 0 ? void 0 : panningState.startMin, extremes.dataMin), halfPointRange ?
- extremes.min :
- axis.toValue(axis.toPixels(extremes.min) -
- axis.minPixelPadding));
- paddedMax = Math.max(H.pick(panningState === null || panningState === void 0 ? void 0 : panningState.startMax, extremes.dataMax), halfPointRange ?
- extremes.max :
- axis.toValue(axis.toPixels(extremes.max) +
- axis.minPixelPadding));
- axis.panningState = panningState;
- // It is not necessary to calculate extremes on ordinal axis,
- // because they are already calculated, so we don't want to
- // override them.
- if (!axis.isOrdinal) {
- // 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 &&
- newMin >= paddedMin &&
- newMax <= paddedMax) {
- axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' });
- if (!chart.resetZoomButton &&
- !hasMapNavigation &&
- // Show reset zoom button only when both newMin and
- // newMax values are between padded axis range.
- newMin !== paddedMin &&
- newMax !== paddedMax &&
- type.match('y')) {
- chart.showResetZoom();
- axis.displayBtn = false;
- }
- 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](https://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect)
- * is `true`, selected points are accumulated on Control, Shift or Cmd
- * clicking the point.
- *
- * @fires Highcharts.Point#event:select
- * @fires Highcharts.Point#event:unselect
- */
- select: function (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.
- */
- onMouseOver: function (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
- * @fires Highcharts.Point#event:mouseOut
- */
- onMouseOut: function () {
- 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
- */
- importEvents: function () {
- 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 (isFunction(event)) {
- addEvent(point, eventType, event);
- }
- });
- this.hasImportedEvents = true;
- }
- },
- /**
- * Set the point's state.
- *
- * @function Highcharts.Point#setState
- *
- * @param {Highcharts.PointStateValue|""} [state]
- * The new state, can be one of `'hover'`, `'select'`, `'inactive'`,
- * or `''` (an empty string), `'normal'` or `undefined` to set to
- * normal state.
- * @param {boolean} [move]
- * State for animation.
- *
- * @fires Highcharts.Point#event:afterSetState
- */
- setState: function (state, move) {
- var point = this, series = point.series, previousState = point.state, stateOptions = (series.options.states[state || 'normal'] ||
- {}), markerOptions = (defaultOptions.plotOptions[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 && pointAttribs.opacity) {
- (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 (markerAttribs) {
- 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 &&
- markerGraphic &&
- markerVisibility !== 'hidden' &&
- !point.isCluster) {
- 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.SVGPathArray}
- * The path definition.
- */
- haloPath: function (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
- * @fires Highcharts.Series#event:mouseOver
- */
- onMouseOver: function () {
- var series = this, chart = series.chart, hoverSeries = chart.hoverSeries, pointer = chart.pointer;
- pointer.setHoverChartIndex();
- // 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 () {
- // 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 {Highcharts.SeriesStateValue|""} [state]
- * The new state, can be either `'hover'`, `'inactive'`, `'select'`,
- * or `''` (an empty string), `'normal'` or `undefined` to set to
- * normal state.
- * @param {boolean} [inherit]
- * Determines if state should be inherited by points too.
- */
- setState: function (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 (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.
- *
- * @fires Highcharts.Series#event:hide
- * @fires Highcharts.Series#event:show
- */
- setVisible: function (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 =
- typeof 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
- * @fires Highcharts.Series#event:show
- */
- show: function () {
- this.setVisible(true);
- },
- /**
- * Hide the series if visible. If the
- * [chart.ignoreHiddenSeries](https://api.highcharts.com/highcharts/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
- * @fires Highcharts.Series#event:hide
- */
- hide: function () {
- 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.
- *
- * @fires Highcharts.Series#event:select
- * @fires Highcharts.Series#event:unselect
- */
- select: function (selected) {
- var series = this;
- series.selected =
- selected =
- this.options.selected = (typeof 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
- });
|