Interaction.js 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170
  1. /* *
  2. *
  3. * (c) 2010-2020 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import Chart from './Chart/Chart.js';
  12. import H from './Globals.js';
  13. import Legend from './Legend.js';
  14. import O from './Options.js';
  15. var defaultOptions = O.defaultOptions;
  16. import Point from '../Core/Series/Point.js';
  17. import U from './Utilities.js';
  18. 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;
  19. /**
  20. * @interface Highcharts.PointEventsOptionsObject
  21. */ /**
  22. * Fires when the point is selected either programmatically or following a click
  23. * on the point. One parameter, `event`, is passed to the function. Returning
  24. * `false` cancels the operation.
  25. * @name Highcharts.PointEventsOptionsObject#select
  26. * @type {Highcharts.PointSelectCallbackFunction|undefined}
  27. */ /**
  28. * Fires when the point is unselected either programmatically or following a
  29. * click on the point. One parameter, `event`, is passed to the function.
  30. * Returning `false` cancels the operation.
  31. * @name Highcharts.PointEventsOptionsObject#unselect
  32. * @type {Highcharts.PointUnselectCallbackFunction|undefined}
  33. */
  34. /**
  35. * Information about the select/unselect event.
  36. *
  37. * @interface Highcharts.PointInteractionEventObject
  38. * @extends global.Event
  39. */ /**
  40. * @name Highcharts.PointInteractionEventObject#accumulate
  41. * @type {boolean}
  42. */
  43. /**
  44. * Gets fired when the point is selected either programmatically or following a
  45. * click on the point.
  46. *
  47. * @callback Highcharts.PointSelectCallbackFunction
  48. *
  49. * @param {Highcharts.Point} this
  50. * Point where the event occured.
  51. *
  52. * @param {Highcharts.PointInteractionEventObject} event
  53. * Event that occured.
  54. */
  55. /**
  56. * Fires when the point is unselected either programmatically or following a
  57. * click on the point.
  58. *
  59. * @callback Highcharts.PointUnselectCallbackFunction
  60. *
  61. * @param {Highcharts.Point} this
  62. * Point where the event occured.
  63. *
  64. * @param {Highcharts.PointInteractionEventObject} event
  65. * Event that occured.
  66. */
  67. import './Series/Series.js';
  68. var hasTouch = H.hasTouch, Series = H.Series, seriesTypes = H.seriesTypes, svg = H.svg, TrackerMixin;
  69. /* eslint-disable valid-jsdoc */
  70. /**
  71. * TrackerMixin for points and graphs.
  72. *
  73. * @private
  74. * @mixin Highcharts.TrackerMixin
  75. */
  76. TrackerMixin = H.TrackerMixin = {
  77. /**
  78. * Draw the tracker for a point.
  79. *
  80. * @private
  81. * @function Highcharts.TrackerMixin.drawTrackerPoint
  82. * @param {Highcharts.Series} this
  83. * @fires Highcharts.Series#event:afterDrawTracker
  84. */
  85. drawTrackerPoint: function () {
  86. var series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function (e) {
  87. var point = pointer.getPointFromEvent(e);
  88. // undefined on graph in scatterchart
  89. if (typeof point !== 'undefined') {
  90. pointer.isDirectTouch = true;
  91. point.onMouseOver(e);
  92. }
  93. }, dataLabels;
  94. // Add reference to the point
  95. series.points.forEach(function (point) {
  96. dataLabels = (isArray(point.dataLabels) ?
  97. point.dataLabels :
  98. (point.dataLabel ? [point.dataLabel] : []));
  99. if (point.graphic) {
  100. point.graphic.element.point = point;
  101. }
  102. dataLabels.forEach(function (dataLabel) {
  103. if (dataLabel.div) {
  104. dataLabel.div.point = point;
  105. }
  106. else {
  107. dataLabel.element.point = point;
  108. }
  109. });
  110. });
  111. // Add the event listeners, we need to do this only once
  112. if (!series._hasTracking) {
  113. series.trackerGroups.forEach(function (key) {
  114. if (series[key]) {
  115. // we don't always have dataLabelsGroup
  116. series[key]
  117. .addClass('highcharts-tracker')
  118. .on('mouseover', onMouseOver)
  119. .on('mouseout', function (e) {
  120. pointer.onTrackerMouseOut(e);
  121. });
  122. if (hasTouch) {
  123. series[key].on('touchstart', onMouseOver);
  124. }
  125. if (!chart.styledMode && series.options.cursor) {
  126. series[key]
  127. .css(css)
  128. .css({ cursor: series.options.cursor });
  129. }
  130. }
  131. });
  132. series._hasTracking = true;
  133. }
  134. fireEvent(this, 'afterDrawTracker');
  135. },
  136. /**
  137. * Draw the tracker object that sits above all data labels and markers to
  138. * track mouse events on the graph or points. For the line type charts
  139. * the tracker uses the same graphPath, but with a greater stroke width
  140. * for better control.
  141. *
  142. * @private
  143. * @function Highcharts.TrackerMixin.drawTrackerGraph
  144. * @param {Highcharts.Series} this
  145. * @fires Highcharts.Series#event:afterDrawTracker
  146. */
  147. drawTrackerGraph: function () {
  148. var series = this, options = series.options, trackByArea = options.trackByArea, trackerPath = [].concat(trackByArea ?
  149. series.areaPath :
  150. series.graphPath),
  151. // trackerPathLength = trackerPath.length,
  152. chart = series.chart, pointer = chart.pointer, renderer = chart.renderer, snap = chart.options.tooltip.snap, tracker = series.tracker, i, onMouseOver = function (e) {
  153. if (chart.hoverSeries !== series) {
  154. series.onMouseOver();
  155. }
  156. },
  157. /*
  158. * Empirical lowest possible opacities for TRACKER_FILL for an
  159. * element to stay invisible but clickable
  160. * IE6: 0.002
  161. * IE7: 0.002
  162. * IE8: 0.002
  163. * IE9: 0.00000000001 (unlimited)
  164. * IE10: 0.0001 (exporting only)
  165. * FF: 0.00000000001 (unlimited)
  166. * Chrome: 0.000001
  167. * Safari: 0.000001
  168. * Opera: 0.00000000001 (unlimited)
  169. */
  170. TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')';
  171. // Draw the tracker
  172. if (tracker) {
  173. tracker.attr({ d: trackerPath });
  174. }
  175. else if (series.graph) { // create
  176. series.tracker = renderer.path(trackerPath)
  177. .attr({
  178. visibility: series.visible ? 'visible' : 'hidden',
  179. zIndex: 2
  180. })
  181. .addClass(trackByArea ?
  182. 'highcharts-tracker-area' :
  183. 'highcharts-tracker-line')
  184. .add(series.group);
  185. if (!chart.styledMode) {
  186. series.tracker.attr({
  187. 'stroke-linecap': 'round',
  188. 'stroke-linejoin': 'round',
  189. stroke: TRACKER_FILL,
  190. fill: trackByArea ? TRACKER_FILL : 'none',
  191. 'stroke-width': series.graph.strokeWidth() +
  192. (trackByArea ? 0 : 2 * snap)
  193. });
  194. }
  195. // The tracker is added to the series group, which is clipped, but
  196. // is covered by the marker group. So the marker group also needs to
  197. // capture events.
  198. [series.tracker, series.markerGroup].forEach(function (tracker) {
  199. tracker.addClass('highcharts-tracker')
  200. .on('mouseover', onMouseOver)
  201. .on('mouseout', function (e) {
  202. pointer.onTrackerMouseOut(e);
  203. });
  204. if (options.cursor && !chart.styledMode) {
  205. tracker.css({ cursor: options.cursor });
  206. }
  207. if (hasTouch) {
  208. tracker.on('touchstart', onMouseOver);
  209. }
  210. });
  211. }
  212. fireEvent(this, 'afterDrawTracker');
  213. }
  214. };
  215. /* End TrackerMixin */
  216. // Add tracking event listener to the series group, so the point graphics
  217. // themselves act as trackers
  218. if (seriesTypes.column) {
  219. /**
  220. * @private
  221. * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.column#drawTracker
  222. */
  223. seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
  224. }
  225. if (seriesTypes.pie) {
  226. /**
  227. * @private
  228. * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.pie#drawTracker
  229. */
  230. seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
  231. }
  232. if (seriesTypes.scatter) {
  233. /**
  234. * @private
  235. * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.scatter#drawTracker
  236. */
  237. seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
  238. }
  239. // Extend Legend for item events.
  240. extend(Legend.prototype, {
  241. /**
  242. * @private
  243. * @function Highcharts.Legend#setItemEvents
  244. * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item
  245. * @param {Highcharts.SVGElement} legendItem
  246. * @param {boolean} [useHTML=false]
  247. * @fires Highcharts.Point#event:legendItemClick
  248. * @fires Highcharts.Series#event:legendItemClick
  249. */
  250. setItemEvents: function (item, legendItem, useHTML) {
  251. var legend = this, boxWrapper = legend.chart.renderer.boxWrapper, isPoint = item instanceof Point, activeClass = 'highcharts-legend-' +
  252. (isPoint ? 'point' : 'series') + '-active', styledMode = legend.chart.styledMode,
  253. // When `useHTML`, the symbol is rendered in other group, so
  254. // we need to apply events listeners to both places
  255. legendItems = useHTML ?
  256. [legendItem, item.legendSymbol] :
  257. [item.legendGroup];
  258. // Set the events on the item group, or in case of useHTML, the item
  259. // itself (#1249)
  260. legendItems.forEach(function (element) {
  261. if (element) {
  262. element
  263. .on('mouseover', function () {
  264. if (item.visible) {
  265. legend.allItems.forEach(function (inactiveItem) {
  266. if (item !== inactiveItem) {
  267. inactiveItem.setState('inactive', !isPoint);
  268. }
  269. });
  270. }
  271. item.setState('hover');
  272. // A CSS class to dim or hide other than the hovered
  273. // series.
  274. // Works only if hovered series is visible (#10071).
  275. if (item.visible) {
  276. boxWrapper.addClass(activeClass);
  277. }
  278. if (!styledMode) {
  279. legendItem.css(legend.options.itemHoverStyle);
  280. }
  281. })
  282. .on('mouseout', function () {
  283. if (!legend.chart.styledMode) {
  284. legendItem.css(merge(item.visible ?
  285. legend.itemStyle :
  286. legend.itemHiddenStyle));
  287. }
  288. legend.allItems.forEach(function (inactiveItem) {
  289. if (item !== inactiveItem) {
  290. inactiveItem.setState('', !isPoint);
  291. }
  292. });
  293. // A CSS class to dim or hide other than the hovered
  294. // series.
  295. boxWrapper.removeClass(activeClass);
  296. item.setState();
  297. })
  298. .on('click', function (event) {
  299. var strLegendItemClick = 'legendItemClick', fnLegendItemClick = function () {
  300. if (item.setVisible) {
  301. item.setVisible();
  302. }
  303. // Reset inactive state
  304. legend.allItems.forEach(function (inactiveItem) {
  305. if (item !== inactiveItem) {
  306. inactiveItem.setState(item.visible ? 'inactive' : '', !isPoint);
  307. }
  308. });
  309. };
  310. // A CSS class to dim or hide other than the hovered
  311. // series. Event handling in iOS causes the activeClass
  312. // to be added prior to click in some cases (#7418).
  313. boxWrapper.removeClass(activeClass);
  314. // Pass over the click/touch event. #4.
  315. event = {
  316. browserEvent: event
  317. };
  318. // click the name or symbol
  319. if (item.firePointEvent) { // point
  320. item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
  321. }
  322. else {
  323. fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
  324. }
  325. });
  326. }
  327. });
  328. },
  329. /**
  330. * @private
  331. * @function Highcharts.Legend#createCheckboxForItem
  332. * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item
  333. * @fires Highcharts.Series#event:checkboxClick
  334. */
  335. createCheckboxForItem: function (item) {
  336. var legend = this;
  337. item.checkbox = createElement('input', {
  338. type: 'checkbox',
  339. className: 'highcharts-legend-checkbox',
  340. checked: item.selected,
  341. defaultChecked: item.selected // required by IE7
  342. }, legend.options.itemCheckboxStyle, legend.chart.container);
  343. addEvent(item.checkbox, 'click', function (event) {
  344. var target = event.target;
  345. fireEvent(item.series || item, 'checkboxClick', {
  346. checked: target.checked,
  347. item: item
  348. }, function () {
  349. item.select();
  350. });
  351. });
  352. }
  353. });
  354. // Extend the Chart object with interaction
  355. extend(Chart.prototype, /** @lends Chart.prototype */ {
  356. /**
  357. * Display the zoom button, so users can reset zoom to the default view
  358. * settings.
  359. *
  360. * @function Highcharts.Chart#showResetZoom
  361. *
  362. * @fires Highcharts.Chart#event:afterShowResetZoom
  363. * @fires Highcharts.Chart#event:beforeShowResetZoom
  364. */
  365. showResetZoom: function () {
  366. var chart = this, lang = defaultOptions.lang, btnOptions = chart.options.chart.resetZoomButton, theme = btnOptions.theme, states = theme.states, alignTo = (btnOptions.relativeTo === 'chart' ||
  367. btnOptions.relativeTo === 'spaceBox' ?
  368. null :
  369. 'plotBox');
  370. /**
  371. * @private
  372. */
  373. function zoomOut() {
  374. chart.zoomOut();
  375. }
  376. fireEvent(this, 'beforeShowResetZoom', null, function () {
  377. chart.resetZoomButton = chart.renderer
  378. .button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
  379. .attr({
  380. align: btnOptions.position.align,
  381. title: lang.resetZoomTitle
  382. })
  383. .addClass('highcharts-reset-zoom')
  384. .add()
  385. .align(btnOptions.position, false, alignTo);
  386. });
  387. fireEvent(this, 'afterShowResetZoom');
  388. },
  389. /**
  390. * Zoom the chart out after a user has zoomed in. See also
  391. * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes).
  392. *
  393. * @function Highcharts.Chart#zoomOut
  394. *
  395. * @fires Highcharts.Chart#event:selection
  396. */
  397. zoomOut: function () {
  398. fireEvent(this, 'selection', { resetSelection: true }, this.zoom);
  399. },
  400. /**
  401. * Zoom into a given portion of the chart given by axis coordinates.
  402. *
  403. * @private
  404. * @function Highcharts.Chart#zoom
  405. * @param {Highcharts.SelectEventObject} event
  406. */
  407. zoom: function (event) {
  408. var chart = this, hasZoomed, pointer = chart.pointer, displayButton = false, mouseDownPos = chart.inverted ? pointer.mouseDownX : pointer.mouseDownY, resetZoomButton;
  409. // If zoom is called with no arguments, reset the axes
  410. if (!event || event.resetSelection) {
  411. chart.axes.forEach(function (axis) {
  412. hasZoomed = axis.zoom();
  413. });
  414. pointer.initiated = false; // #6804
  415. }
  416. else { // else, zoom in on all axes
  417. event.xAxis.concat(event.yAxis).forEach(function (axisData) {
  418. var axis = axisData.axis, axisStartPos = chart.inverted ? axis.left : axis.top, axisEndPos = chart.inverted ?
  419. axisStartPos + axis.width : axisStartPos + axis.height, isXAxis = axis.isXAxis, isWithinPane = false;
  420. // Check if zoomed area is within the pane (#1289).
  421. // In case of multiple panes only one pane should be zoomed.
  422. if ((!isXAxis &&
  423. mouseDownPos >= axisStartPos &&
  424. mouseDownPos <= axisEndPos) ||
  425. isXAxis ||
  426. !defined(mouseDownPos)) {
  427. isWithinPane = true;
  428. }
  429. // don't zoom more than minRange
  430. if (pointer[isXAxis ? 'zoomX' : 'zoomY'] && isWithinPane) {
  431. hasZoomed = axis.zoom(axisData.min, axisData.max);
  432. if (axis.displayBtn) {
  433. displayButton = true;
  434. }
  435. }
  436. });
  437. }
  438. // Show or hide the Reset zoom button
  439. resetZoomButton = chart.resetZoomButton;
  440. if (displayButton && !resetZoomButton) {
  441. chart.showResetZoom();
  442. }
  443. else if (!displayButton && isObject(resetZoomButton)) {
  444. chart.resetZoomButton = resetZoomButton.destroy();
  445. }
  446. // Redraw
  447. if (hasZoomed) {
  448. chart.redraw(pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100));
  449. }
  450. },
  451. /**
  452. * Pan the chart by dragging the mouse across the pane. This function is
  453. * called on mouse move, and the distance to pan is computed from chartX
  454. * compared to the first chartX position in the dragging operation.
  455. *
  456. * @private
  457. * @function Highcharts.Chart#pan
  458. * @param {Highcharts.PointerEventObject} e
  459. * @param {string} panning
  460. */
  461. pan: function (e, panning) {
  462. var chart = this, hoverPoints = chart.hoverPoints, panningOptions, chartOptions = chart.options.chart, hasMapNavigation = chart.options.mapNavigation &&
  463. chart.options.mapNavigation.enabled, doRedraw, type;
  464. if (typeof panning === 'object') {
  465. panningOptions = panning;
  466. }
  467. else {
  468. panningOptions = {
  469. enabled: panning,
  470. type: 'x'
  471. };
  472. }
  473. if (chartOptions && chartOptions.panning) {
  474. chartOptions.panning = panningOptions;
  475. }
  476. type = panningOptions.type;
  477. fireEvent(this, 'pan', { originalEvent: e }, function () {
  478. // remove active points for shared tooltip
  479. if (hoverPoints) {
  480. hoverPoints.forEach(function (point) {
  481. point.setState();
  482. });
  483. }
  484. // panning axis mapping
  485. var xy = [1]; // x
  486. if (type === 'xy') {
  487. xy = [1, 0];
  488. }
  489. else if (type === 'y') {
  490. xy = [0];
  491. }
  492. xy.forEach(function (isX) {
  493. 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) ||
  494. (!axis.reversed && chart.inverted) ?
  495. -1 :
  496. 1, extremes = axis.getExtremes(), panMin = axis.toValue(startPos - mousePos, true) +
  497. halfPointRange * pointRangeDirection, panMax = axis.toValue(startPos + axis.len - mousePos, true) -
  498. halfPointRange * pointRangeDirection, flipped = panMax < panMin, newMin = flipped ? panMax : panMin, newMax = flipped ? panMin : panMax, hasVerticalPanning = axis.hasVerticalPanning(), paddedMin, paddedMax, spill, panningState = axis.panningState;
  499. // General calculations of panning state.
  500. // This is related to using vertical panning. (#11315).
  501. axis.series.forEach(function (series) {
  502. if (hasVerticalPanning &&
  503. !isX && (!panningState || panningState.isDirty)) {
  504. var processedData = series.getProcessedData(true), dataExtremes = series.getExtremes(processedData.yData, true);
  505. if (!panningState) {
  506. panningState = {
  507. startMin: Number.MAX_VALUE,
  508. startMax: -Number.MAX_VALUE
  509. };
  510. }
  511. if (isNumber(dataExtremes.dataMin) &&
  512. isNumber(dataExtremes.dataMax)) {
  513. panningState.startMin = Math.min(dataExtremes.dataMin, panningState.startMin);
  514. panningState.startMax = Math.max(dataExtremes.dataMax, panningState.startMax);
  515. }
  516. }
  517. });
  518. paddedMin = Math.min(H.pick(panningState === null || panningState === void 0 ? void 0 : panningState.startMin, extremes.dataMin), halfPointRange ?
  519. extremes.min :
  520. axis.toValue(axis.toPixels(extremes.min) -
  521. axis.minPixelPadding));
  522. paddedMax = Math.max(H.pick(panningState === null || panningState === void 0 ? void 0 : panningState.startMax, extremes.dataMax), halfPointRange ?
  523. extremes.max :
  524. axis.toValue(axis.toPixels(extremes.max) +
  525. axis.minPixelPadding));
  526. axis.panningState = panningState;
  527. // It is not necessary to calculate extremes on ordinal axis,
  528. // because they are already calculated, so we don't want to
  529. // override them.
  530. if (!axis.isOrdinal) {
  531. // If the new range spills over, either to the min or max,
  532. // adjust the new range.
  533. spill = paddedMin - newMin;
  534. if (spill > 0) {
  535. newMax += spill;
  536. newMin = paddedMin;
  537. }
  538. spill = newMax - paddedMax;
  539. if (spill > 0) {
  540. newMax = paddedMax;
  541. newMin -= spill;
  542. }
  543. // Set new extremes if they are actually new
  544. if (axis.series.length &&
  545. newMin !== extremes.min &&
  546. newMax !== extremes.max &&
  547. newMin >= paddedMin &&
  548. newMax <= paddedMax) {
  549. axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' });
  550. if (!chart.resetZoomButton &&
  551. !hasMapNavigation &&
  552. // Show reset zoom button only when both newMin and
  553. // newMax values are between padded axis range.
  554. newMin !== paddedMin &&
  555. newMax !== paddedMax &&
  556. type.match('y')) {
  557. chart.showResetZoom();
  558. axis.displayBtn = false;
  559. }
  560. doRedraw = true;
  561. }
  562. // set new reference for next run:
  563. chart[mouseDown] = mousePos;
  564. }
  565. });
  566. if (doRedraw) {
  567. chart.redraw(false);
  568. }
  569. css(chart.container, { cursor: 'move' });
  570. });
  571. }
  572. });
  573. // Extend the Point object with interaction
  574. extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
  575. /**
  576. * Toggle the selection status of a point.
  577. *
  578. * @see Highcharts.Chart#getSelectedPoints
  579. *
  580. * @sample highcharts/members/point-select/
  581. * Select a point from a button
  582. * @sample highcharts/chart/events-selection-points/
  583. * Select a range of points through a drag selection
  584. * @sample maps/series/data-id/
  585. * Select a point in Highmaps
  586. *
  587. * @function Highcharts.Point#select
  588. *
  589. * @param {boolean} [selected]
  590. * When `true`, the point is selected. When `false`, the point is
  591. * unselected. When `null` or `undefined`, the selection state is toggled.
  592. *
  593. * @param {boolean} [accumulate=false]
  594. * When `true`, the selection is added to other selected points.
  595. * When `false`, other selected points are deselected. Internally in
  596. * Highcharts, when
  597. * [allowPointSelect](https://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect)
  598. * is `true`, selected points are accumulated on Control, Shift or Cmd
  599. * clicking the point.
  600. *
  601. * @fires Highcharts.Point#event:select
  602. * @fires Highcharts.Point#event:unselect
  603. */
  604. select: function (selected, accumulate) {
  605. var point = this, series = point.series, chart = series.chart;
  606. selected = pick(selected, !point.selected);
  607. this.selectedStaging = selected;
  608. // fire the event with the default handler
  609. point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
  610. /**
  611. * Whether the point is selected or not.
  612. *
  613. * @see Point#select
  614. * @see Chart#getSelectedPoints
  615. *
  616. * @name Highcharts.Point#selected
  617. * @type {boolean}
  618. */
  619. point.selected = point.options.selected = selected;
  620. series.options.data[series.data.indexOf(point)] =
  621. point.options;
  622. point.setState(selected && 'select');
  623. // unselect all other points unless Ctrl or Cmd + click
  624. if (!accumulate) {
  625. chart.getSelectedPoints().forEach(function (loopPoint) {
  626. var loopSeries = loopPoint.series;
  627. if (loopPoint.selected && loopPoint !== point) {
  628. loopPoint.selected = loopPoint.options.selected =
  629. false;
  630. loopSeries.options.data[loopSeries.data.indexOf(loopPoint)] = loopPoint.options;
  631. // Programatically selecting a point should restore
  632. // normal state, but when click happened on other
  633. // point, set inactive state to match other points
  634. loopPoint.setState(chart.hoverPoints &&
  635. loopSeries.options.inactiveOtherPoints ?
  636. 'inactive' : '');
  637. loopPoint.firePointEvent('unselect');
  638. }
  639. });
  640. }
  641. });
  642. delete this.selectedStaging;
  643. },
  644. /**
  645. * Runs on mouse over the point. Called internally from mouse and touch
  646. * events.
  647. *
  648. * @function Highcharts.Point#onMouseOver
  649. *
  650. * @param {Highcharts.PointerEventObject} [e]
  651. * The event arguments.
  652. */
  653. onMouseOver: function (e) {
  654. var point = this, series = point.series, chart = series.chart, pointer = chart.pointer;
  655. e = e ?
  656. pointer.normalize(e) :
  657. // In cases where onMouseOver is called directly without an event
  658. pointer.getChartCoordinatesFromPoint(point, chart.inverted);
  659. pointer.runPointActions(e, point);
  660. },
  661. /**
  662. * Runs on mouse out from the point. Called internally from mouse and touch
  663. * events.
  664. *
  665. * @function Highcharts.Point#onMouseOut
  666. * @fires Highcharts.Point#event:mouseOut
  667. */
  668. onMouseOut: function () {
  669. var point = this, chart = point.series.chart;
  670. point.firePointEvent('mouseOut');
  671. if (!point.series.options.inactiveOtherPoints) {
  672. (chart.hoverPoints || []).forEach(function (p) {
  673. p.setState();
  674. });
  675. }
  676. chart.hoverPoints = chart.hoverPoint = null;
  677. },
  678. /**
  679. * Import events from the series' and point's options. Only do it on
  680. * demand, to save processing time on hovering.
  681. *
  682. * @private
  683. * @function Highcharts.Point#importEvents
  684. */
  685. importEvents: function () {
  686. if (!this.hasImportedEvents) {
  687. var point = this, options = merge(point.series.options.point, point.options), events = options.events;
  688. point.events = events;
  689. objectEach(events, function (event, eventType) {
  690. if (isFunction(event)) {
  691. addEvent(point, eventType, event);
  692. }
  693. });
  694. this.hasImportedEvents = true;
  695. }
  696. },
  697. /**
  698. * Set the point's state.
  699. *
  700. * @function Highcharts.Point#setState
  701. *
  702. * @param {Highcharts.PointStateValue|""} [state]
  703. * The new state, can be one of `'hover'`, `'select'`, `'inactive'`,
  704. * or `''` (an empty string), `'normal'` or `undefined` to set to
  705. * normal state.
  706. * @param {boolean} [move]
  707. * State for animation.
  708. *
  709. * @fires Highcharts.Point#event:afterSetState
  710. */
  711. setState: function (state, move) {
  712. var point = this, series = point.series, previousState = point.state, stateOptions = (series.options.states[state || 'normal'] ||
  713. {}), markerOptions = (defaultOptions.plotOptions[series.type].marker &&
  714. series.options.marker), normalDisabled = (markerOptions && markerOptions.enabled === false), markerStateOptions = ((markerOptions &&
  715. markerOptions.states &&
  716. 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;
  717. state = state || ''; // empty string
  718. if (
  719. // already has this state
  720. (state === point.state && !move) ||
  721. // selected points don't respond to hover
  722. (point.selected && state !== 'select') ||
  723. // series' state options is disabled
  724. (stateOptions.enabled === false) ||
  725. // general point marker's state options is disabled
  726. (state && (stateDisabled ||
  727. (normalDisabled &&
  728. markerStateOptions.enabled === false))) ||
  729. // individual point marker's state options is disabled
  730. (state &&
  731. pointMarker.states &&
  732. pointMarker.states[state] &&
  733. pointMarker.states[state].enabled === false) // #1610
  734. ) {
  735. return;
  736. }
  737. point.state = state;
  738. if (hasMarkers) {
  739. markerAttribs = series.markerAttribs(point, state);
  740. }
  741. // Apply hover styles to the existing point
  742. if (point.graphic) {
  743. if (previousState) {
  744. point.graphic.removeClass('highcharts-point-' + previousState);
  745. }
  746. if (state) {
  747. point.graphic.addClass('highcharts-point-' + state);
  748. }
  749. if (!chart.styledMode) {
  750. pointAttribs = series.pointAttribs(point, state);
  751. pointAttribsAnimation = pick(chart.options.chart.animation, stateOptions.animation);
  752. // Some inactive points (e.g. slices in pie) should apply
  753. // oppacity also for it's labels
  754. if (series.options.inactiveOtherPoints && pointAttribs.opacity) {
  755. (point.dataLabels || []).forEach(function (label) {
  756. if (label) {
  757. label.animate({
  758. opacity: pointAttribs.opacity
  759. }, pointAttribsAnimation);
  760. }
  761. });
  762. if (point.connector) {
  763. point.connector.animate({
  764. opacity: pointAttribs.opacity
  765. }, pointAttribsAnimation);
  766. }
  767. }
  768. point.graphic.animate(pointAttribs, pointAttribsAnimation);
  769. }
  770. if (markerAttribs) {
  771. point.graphic.animate(markerAttribs, pick(
  772. // Turn off globally:
  773. chart.options.chart.animation, markerStateOptions.animation, markerOptions.animation));
  774. }
  775. // Zooming in from a range with no markers to a range with markers
  776. if (stateMarkerGraphic) {
  777. stateMarkerGraphic.hide();
  778. }
  779. }
  780. else {
  781. // if a graphic is not applied to each point in the normal state,
  782. // create a shared graphic for the hover state
  783. if (state && markerStateOptions) {
  784. newSymbol = pointMarker.symbol || series.symbol;
  785. // If the point has another symbol than the previous one, throw
  786. // away the state marker graphic and force a new one (#1459)
  787. if (stateMarkerGraphic &&
  788. stateMarkerGraphic.currentSymbol !== newSymbol) {
  789. stateMarkerGraphic = stateMarkerGraphic.destroy();
  790. }
  791. // Add a new state marker graphic
  792. if (markerAttribs) {
  793. if (!stateMarkerGraphic) {
  794. if (newSymbol) {
  795. series.stateMarkerGraphic = stateMarkerGraphic =
  796. chart.renderer
  797. .symbol(newSymbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height)
  798. .add(series.markerGroup);
  799. stateMarkerGraphic.currentSymbol = newSymbol;
  800. }
  801. // Move the existing graphic
  802. }
  803. else {
  804. stateMarkerGraphic[move ? 'animate' : 'attr']({
  805. x: markerAttribs.x,
  806. y: markerAttribs.y
  807. });
  808. }
  809. }
  810. if (!chart.styledMode && stateMarkerGraphic) {
  811. stateMarkerGraphic.attr(series.pointAttribs(point, state));
  812. }
  813. }
  814. if (stateMarkerGraphic) {
  815. stateMarkerGraphic[state && point.isInside ? 'show' : 'hide'](); // #2450
  816. stateMarkerGraphic.element.point = point; // #4310
  817. }
  818. }
  819. // Show me your halo
  820. haloOptions = stateOptions.halo;
  821. var markerGraphic = (point.graphic || stateMarkerGraphic);
  822. var markerVisibility = (markerGraphic && markerGraphic.visibility || 'inherit');
  823. if (haloOptions &&
  824. haloOptions.size &&
  825. markerGraphic &&
  826. markerVisibility !== 'hidden' &&
  827. !point.isCluster) {
  828. if (!halo) {
  829. series.halo = halo = chart.renderer.path()
  830. // #5818, #5903, #6705
  831. .add(markerGraphic.parentGroup);
  832. }
  833. halo.show()[move ? 'animate' : 'attr']({
  834. d: point.haloPath(haloOptions.size)
  835. });
  836. halo.attr({
  837. 'class': 'highcharts-halo highcharts-color-' +
  838. pick(point.colorIndex, series.colorIndex) +
  839. (point.className ? ' ' + point.className : ''),
  840. 'visibility': markerVisibility,
  841. 'zIndex': -1 // #4929, #8276
  842. });
  843. halo.point = point; // #6055
  844. if (!chart.styledMode) {
  845. halo.attr(extend({
  846. 'fill': point.color || series.color,
  847. 'fill-opacity': haloOptions.opacity
  848. }, haloOptions.attributes));
  849. }
  850. }
  851. else if (halo && halo.point && halo.point.haloPath) {
  852. // Animate back to 0 on the current halo point (#6055)
  853. halo.animate({ d: halo.point.haloPath(0) }, null,
  854. // Hide after unhovering. The `complete` callback runs in the
  855. // halo's context (#7681).
  856. halo.hide);
  857. }
  858. fireEvent(point, 'afterSetState');
  859. },
  860. /**
  861. * Get the path definition for the halo, which is usually a shadow-like
  862. * circle around the currently hovered point.
  863. *
  864. * @function Highcharts.Point#haloPath
  865. *
  866. * @param {number} size
  867. * The radius of the circular halo.
  868. *
  869. * @return {Highcharts.SVGPathArray}
  870. * The path definition.
  871. */
  872. haloPath: function (size) {
  873. var series = this.series, chart = series.chart;
  874. return chart.renderer.symbols.circle(Math.floor(this.plotX) - size, this.plotY - size, size * 2, size * 2);
  875. }
  876. });
  877. // Extend the Series object with interaction
  878. extend(Series.prototype, /** @lends Highcharts.Series.prototype */ {
  879. /**
  880. * Runs on mouse over the series graphical items.
  881. *
  882. * @function Highcharts.Series#onMouseOver
  883. * @fires Highcharts.Series#event:mouseOver
  884. */
  885. onMouseOver: function () {
  886. var series = this, chart = series.chart, hoverSeries = chart.hoverSeries, pointer = chart.pointer;
  887. pointer.setHoverChartIndex();
  888. // set normal state to previous series
  889. if (hoverSeries && hoverSeries !== series) {
  890. hoverSeries.onMouseOut();
  891. }
  892. // trigger the event, but to save processing time,
  893. // only if defined
  894. if (series.options.events.mouseOver) {
  895. fireEvent(series, 'mouseOver');
  896. }
  897. // hover this
  898. series.setState('hover');
  899. /**
  900. * Contains the original hovered series.
  901. *
  902. * @name Highcharts.Chart#hoverSeries
  903. * @type {Highcharts.Series|null}
  904. */
  905. chart.hoverSeries = series;
  906. },
  907. /**
  908. * Runs on mouse out of the series graphical items.
  909. *
  910. * @function Highcharts.Series#onMouseOut
  911. *
  912. * @fires Highcharts.Series#event:mouseOut
  913. */
  914. onMouseOut: function () {
  915. // trigger the event only if listeners exist
  916. var series = this, options = series.options, chart = series.chart, tooltip = chart.tooltip, hoverPoint = chart.hoverPoint;
  917. // #182, set to null before the mouseOut event fires
  918. chart.hoverSeries = null;
  919. // trigger mouse out on the point, which must be in this series
  920. if (hoverPoint) {
  921. hoverPoint.onMouseOut();
  922. }
  923. // fire the mouse out event
  924. if (series && options.events.mouseOut) {
  925. fireEvent(series, 'mouseOut');
  926. }
  927. // hide the tooltip
  928. if (tooltip &&
  929. !series.stickyTracking &&
  930. (!tooltip.shared || series.noSharedTooltip)) {
  931. tooltip.hide();
  932. }
  933. // Reset all inactive states
  934. chart.series.forEach(function (s) {
  935. s.setState('', true);
  936. });
  937. },
  938. /**
  939. * Set the state of the series. Called internally on mouse interaction
  940. * operations, but it can also be called directly to visually
  941. * highlight a series.
  942. *
  943. * @function Highcharts.Series#setState
  944. *
  945. * @param {Highcharts.SeriesStateValue|""} [state]
  946. * The new state, can be either `'hover'`, `'inactive'`, `'select'`,
  947. * or `''` (an empty string), `'normal'` or `undefined` to set to
  948. * normal state.
  949. * @param {boolean} [inherit]
  950. * Determines if state should be inherited by points too.
  951. */
  952. setState: function (state, inherit) {
  953. var series = this, options = series.options, graph = series.graph, inactiveOtherPoints = options.inactiveOtherPoints, stateOptions = options.states, lineWidth = options.lineWidth, opacity = options.opacity,
  954. // By default a quick animation to hover/inactive,
  955. // slower to un-hover
  956. stateAnimation = pick((stateOptions[state || 'normal'] &&
  957. stateOptions[state || 'normal'].animation), series.chart.options.chart.animation), attribs, i = 0;
  958. state = state || '';
  959. if (series.state !== state) {
  960. // Toggle class names
  961. [
  962. series.group,
  963. series.markerGroup,
  964. series.dataLabelsGroup
  965. ].forEach(function (group) {
  966. if (group) {
  967. // Old state
  968. if (series.state) {
  969. group.removeClass('highcharts-series-' + series.state);
  970. }
  971. // New state
  972. if (state) {
  973. group.addClass('highcharts-series-' + state);
  974. }
  975. }
  976. });
  977. series.state = state;
  978. if (!series.chart.styledMode) {
  979. if (stateOptions[state] &&
  980. stateOptions[state].enabled === false) {
  981. return;
  982. }
  983. if (state) {
  984. lineWidth = (stateOptions[state].lineWidth ||
  985. lineWidth + (stateOptions[state].lineWidthPlus || 0)); // #4035
  986. opacity = pick(stateOptions[state].opacity, opacity);
  987. }
  988. if (graph && !graph.dashstyle) {
  989. attribs = {
  990. 'stroke-width': lineWidth
  991. };
  992. // Animate the graph stroke-width.
  993. graph.animate(attribs, stateAnimation);
  994. while (series['zone-graph-' + i]) {
  995. series['zone-graph-' + i].attr(attribs);
  996. i = i + 1;
  997. }
  998. }
  999. // For some types (pie, networkgraph, sankey) opacity is
  1000. // resolved on a point level
  1001. if (!inactiveOtherPoints) {
  1002. [
  1003. series.group,
  1004. series.markerGroup,
  1005. series.dataLabelsGroup,
  1006. series.labelBySeries
  1007. ].forEach(function (group) {
  1008. if (group) {
  1009. group.animate({
  1010. opacity: opacity
  1011. }, stateAnimation);
  1012. }
  1013. });
  1014. }
  1015. }
  1016. }
  1017. // Don't loop over points on a series that doesn't apply inactive state
  1018. // to siblings markers (e.g. line, column)
  1019. if (inherit && inactiveOtherPoints && series.points) {
  1020. series.setAllPointsToState(state);
  1021. }
  1022. },
  1023. /**
  1024. * Set the state for all points in the series.
  1025. *
  1026. * @function Highcharts.Series#setAllPointsToState
  1027. *
  1028. * @private
  1029. *
  1030. * @param {string} [state]
  1031. * Can be either `hover` or undefined to set to normal state.
  1032. */
  1033. setAllPointsToState: function (state) {
  1034. this.points.forEach(function (point) {
  1035. if (point.setState) {
  1036. point.setState(state);
  1037. }
  1038. });
  1039. },
  1040. /**
  1041. * Show or hide the series.
  1042. *
  1043. * @function Highcharts.Series#setVisible
  1044. *
  1045. * @param {boolean} [visible]
  1046. * True to show the series, false to hide. If undefined, the visibility is
  1047. * toggled.
  1048. *
  1049. * @param {boolean} [redraw=true]
  1050. * Whether to redraw the chart after the series is altered. If doing more
  1051. * operations on the chart, it is a good idea to set redraw to false and
  1052. * call {@link Chart#redraw|chart.redraw()} after.
  1053. *
  1054. * @fires Highcharts.Series#event:hide
  1055. * @fires Highcharts.Series#event:show
  1056. */
  1057. setVisible: function (vis, redraw) {
  1058. var series = this, chart = series.chart, legendItem = series.legendItem, showOrHide, ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, oldVisibility = series.visible;
  1059. // if called without an argument, toggle visibility
  1060. series.visible =
  1061. vis =
  1062. series.options.visible =
  1063. series.userOptions.visible =
  1064. typeof vis === 'undefined' ? !oldVisibility : vis; // #5618
  1065. showOrHide = vis ? 'show' : 'hide';
  1066. // show or hide elements
  1067. [
  1068. 'group',
  1069. 'dataLabelsGroup',
  1070. 'markerGroup',
  1071. 'tracker',
  1072. 'tt'
  1073. ].forEach(function (key) {
  1074. if (series[key]) {
  1075. series[key][showOrHide]();
  1076. }
  1077. });
  1078. // hide tooltip (#1361)
  1079. if (chart.hoverSeries === series ||
  1080. (chart.hoverPoint && chart.hoverPoint.series) === series) {
  1081. series.onMouseOut();
  1082. }
  1083. if (legendItem) {
  1084. chart.legend.colorizeItem(series, vis);
  1085. }
  1086. // rescale or adapt to resized chart
  1087. series.isDirty = true;
  1088. // in a stack, all other series are affected
  1089. if (series.options.stacking) {
  1090. chart.series.forEach(function (otherSeries) {
  1091. if (otherSeries.options.stacking && otherSeries.visible) {
  1092. otherSeries.isDirty = true;
  1093. }
  1094. });
  1095. }
  1096. // show or hide linked series
  1097. series.linkedSeries.forEach(function (otherSeries) {
  1098. otherSeries.setVisible(vis, false);
  1099. });
  1100. if (ignoreHiddenSeries) {
  1101. chart.isDirtyBox = true;
  1102. }
  1103. fireEvent(series, showOrHide);
  1104. if (redraw !== false) {
  1105. chart.redraw();
  1106. }
  1107. },
  1108. /**
  1109. * Show the series if hidden.
  1110. *
  1111. * @sample highcharts/members/series-hide/
  1112. * Toggle visibility from a button
  1113. *
  1114. * @function Highcharts.Series#show
  1115. * @fires Highcharts.Series#event:show
  1116. */
  1117. show: function () {
  1118. this.setVisible(true);
  1119. },
  1120. /**
  1121. * Hide the series if visible. If the
  1122. * [chart.ignoreHiddenSeries](https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries)
  1123. * option is true, the chart is redrawn without this series.
  1124. *
  1125. * @sample highcharts/members/series-hide/
  1126. * Toggle visibility from a button
  1127. *
  1128. * @function Highcharts.Series#hide
  1129. * @fires Highcharts.Series#event:hide
  1130. */
  1131. hide: function () {
  1132. this.setVisible(false);
  1133. },
  1134. /**
  1135. * Select or unselect the series. This means its
  1136. * {@link Highcharts.Series.selected|selected}
  1137. * property is set, the checkbox in the legend is toggled and when selected,
  1138. * the series is returned by the {@link Highcharts.Chart#getSelectedSeries}
  1139. * function.
  1140. *
  1141. * @sample highcharts/members/series-select/
  1142. * Select a series from a button
  1143. *
  1144. * @function Highcharts.Series#select
  1145. *
  1146. * @param {boolean} [selected]
  1147. * True to select the series, false to unselect. If undefined, the selection
  1148. * state is toggled.
  1149. *
  1150. * @fires Highcharts.Series#event:select
  1151. * @fires Highcharts.Series#event:unselect
  1152. */
  1153. select: function (selected) {
  1154. var series = this;
  1155. series.selected =
  1156. selected =
  1157. this.options.selected = (typeof selected === 'undefined' ?
  1158. !series.selected :
  1159. selected);
  1160. if (series.checkbox) {
  1161. series.checkbox.checked = selected;
  1162. }
  1163. fireEvent(series, selected ? 'select' : 'unselect');
  1164. },
  1165. /**
  1166. * @private
  1167. * @borrows Highcharts.TrackerMixin.drawTrackerGraph as Highcharts.Series#drawTracker
  1168. */
  1169. drawTracker: TrackerMixin.drawTrackerGraph
  1170. });