Pointer.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. /* *
  2. *
  3. * (c) 2010-2019 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 Highcharts from './Globals.js';
  12. /**
  13. * One position in relation to an axis.
  14. *
  15. * @interface Highcharts.PointerAxisCoordinateObject
  16. */ /**
  17. * Related axis.
  18. *
  19. * @name Highcharts.PointerAxisCoordinateObject#axis
  20. * @type {Highcharts.Axis}
  21. */ /**
  22. * Axis value.
  23. *
  24. * @name Highcharts.PointerAxisCoordinateObject#value
  25. * @type {number}
  26. */
  27. /**
  28. * Positions in terms of axis values.
  29. *
  30. * @interface Highcharts.PointerAxisCoordinatesObject
  31. */ /**
  32. * Positions on the x-axis.
  33. * @name Highcharts.PointerAxisCoordinatesObject#xAxis
  34. * @type {Array<Highcharts.PointerAxisCoordinateObject>}
  35. */ /**
  36. * Positions on the y-axis.
  37. * @name Highcharts.PointerAxisCoordinatesObject#yAxis
  38. * @type {Array<Highcharts.PointerAxisCoordinateObject>}
  39. */
  40. /**
  41. * Pointer coordinates.
  42. *
  43. * @interface Highcharts.PointerCoordinatesObject
  44. */ /**
  45. * @name Highcharts.PointerCoordinatesObject#chartX
  46. * @type {number}
  47. */ /**
  48. * @name Highcharts.PointerCoordinatesObject#chartY
  49. * @type {number}
  50. */
  51. /**
  52. * A native browser mouse or touch event, extended with position information
  53. * relative to the {@link Chart.container}.
  54. *
  55. * @interface Highcharts.PointerEventObject
  56. * @extends global.PointerEvent
  57. */ /**
  58. * The X coordinate of the pointer interaction relative to the chart.
  59. *
  60. * @name Highcharts.PointerEventObject#chartX
  61. * @type {number}
  62. */ /**
  63. * The Y coordinate of the pointer interaction relative to the chart.
  64. *
  65. * @name Highcharts.PointerEventObject#chartY
  66. * @type {number}
  67. */
  68. /**
  69. * Axis-specific data of a selection.
  70. *
  71. * @interface Highcharts.SelectDataObject
  72. */ /**
  73. * @name Highcharts.SelectDataObject#axis
  74. * @type {Highcharts.Axis}
  75. */ /**
  76. * @name Highcharts.SelectDataObject#max
  77. * @type {number}
  78. */ /**
  79. * @name Highcharts.SelectDataObject#min
  80. * @type {number}
  81. */
  82. /**
  83. * Object for select events.
  84. *
  85. * @interface Highcharts.SelectEventObject
  86. */ /**
  87. * @name Highcharts.SelectEventObject#originalEvent
  88. * @type {global.Event}
  89. */ /**
  90. * @name Highcharts.SelectEventObject#xAxis
  91. * @type {Array<Highcharts.SelectDataObject>}
  92. */ /**
  93. * @name Highcharts.SelectEventObject#yAxis
  94. * @type {Array<Highcharts.SelectDataObject>}
  95. */
  96. import U from './Utilities.js';
  97. var attr = U.attr, defined = U.defined, extend = U.extend, isNumber = U.isNumber, isObject = U.isObject, objectEach = U.objectEach, offset = U.offset, pick = U.pick, splat = U.splat;
  98. import './Tooltip.js';
  99. import './Color.js';
  100. var H = Highcharts, addEvent = H.addEvent, charts = H.charts, color = H.color, css = H.css, find = H.find, fireEvent = H.fireEvent, Tooltip = H.Tooltip;
  101. /* eslint-disable no-invalid-this, valid-jsdoc */
  102. /**
  103. * The mouse and touch tracker object. Each {@link Chart} item has one
  104. * assosiated Pointer item that can be accessed from the {@link Chart.pointer}
  105. * property.
  106. *
  107. * @class
  108. * @name Highcharts.Pointer
  109. *
  110. * @param {Highcharts.Chart} chart
  111. * The Chart instance.
  112. *
  113. * @param {Highcharts.Options} options
  114. * The root options object. The pointer uses options from the chart and
  115. * tooltip structures.
  116. */
  117. Highcharts.Pointer = function (chart, options) {
  118. this.init(chart, options);
  119. };
  120. Highcharts.Pointer.prototype = {
  121. /**
  122. * Initialize the Pointer.
  123. *
  124. * @private
  125. * @function Highcharts.Pointer#init
  126. *
  127. * @param {Highcharts.Chart} chart
  128. * The Chart instance.
  129. *
  130. * @param {Highcharts.Options} options
  131. * The root options object. The pointer uses options from the chart
  132. * and tooltip structures.
  133. *
  134. * @return {void}
  135. */
  136. init: function (chart, options) {
  137. // Store references
  138. this.options = options;
  139. this.chart = chart;
  140. // Do we need to handle click on a touch device?
  141. this.runChartClick =
  142. options.chart.events &&
  143. !!options.chart.events.click;
  144. this.pinchDown = [];
  145. this.lastValidTouch = {};
  146. if (Tooltip) {
  147. /**
  148. * Tooltip object for points of series.
  149. *
  150. * @name Highcharts.Chart#tooltip
  151. * @type {Highcharts.Tooltip}
  152. */
  153. chart.tooltip = new Tooltip(chart, options.tooltip);
  154. this.followTouchMove = pick(options.tooltip.followTouchMove, true);
  155. }
  156. this.setDOMEvents();
  157. },
  158. /**
  159. * Resolve the zoomType option, this is reset on all touch start and mouse
  160. * down events.
  161. *
  162. * @private
  163. * @function Highcharts.Pointer#zoomOption
  164. *
  165. * @param {global.Event} e
  166. * Event object.
  167. *
  168. * @param {void}
  169. */
  170. zoomOption: function (e) {
  171. var chart = this.chart, options = chart.options.chart, zoomType = options.zoomType || '', inverted = chart.inverted, zoomX, zoomY;
  172. // Look for the pinchType option
  173. if (/touch/.test(e.type)) {
  174. zoomType = pick(options.pinchType, zoomType);
  175. }
  176. this.zoomX = zoomX = /x/.test(zoomType);
  177. this.zoomY = zoomY = /y/.test(zoomType);
  178. this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
  179. this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
  180. this.hasZoom = zoomX || zoomY;
  181. },
  182. /**
  183. * Return the cached chartPosition if it is available on the Pointer,
  184. * otherwise find it. Running offset is quite expensive, so it should be
  185. * avoided when we know the chart hasn't moved.
  186. *
  187. * @function Highcharts.Pointer#getChartPosition
  188. *
  189. * @return {Highcharts.OffsetObject}
  190. * The offset of the chart container within the page
  191. */
  192. getChartPosition: function () {
  193. var chart = this.chart;
  194. var container = chart.scrollingContainer || chart.container;
  195. return (this.chartPosition ||
  196. (this.chartPosition = offset(container)));
  197. },
  198. /**
  199. * Takes a browser event object and extends it with custom Highcharts
  200. * properties `chartX` and `chartY` in order to work on the internal
  201. * coordinate system.
  202. *
  203. * @function Highcharts.Pointer#normalize
  204. *
  205. * @param {PointerEvent|TouchEvent} e
  206. * Event object in standard browsers.
  207. *
  208. * @param {Highcharts.OffsetObject} [chartPosition]
  209. * Additional chart offset.
  210. *
  211. * @return {Highcharts.PointerEventObject}
  212. * A browser event with extended properties `chartX` and `chartY`.
  213. */
  214. normalize: function (e, chartPosition) {
  215. var ePos;
  216. // iOS (#2757)
  217. ePos = e.touches ?
  218. (e.touches.length ?
  219. e.touches.item(0) :
  220. e.changedTouches[0]) :
  221. e;
  222. // Get mouse position
  223. if (!chartPosition) {
  224. chartPosition = this.getChartPosition();
  225. }
  226. var chartX = ePos.pageX - chartPosition.left, chartY = ePos.pageY - chartPosition.top;
  227. // #11329 - when there is scaling on a parent element, we need to take
  228. // this into account
  229. var containerScaling = this.chart.containerScaling;
  230. if (containerScaling) {
  231. chartX /= containerScaling.scaleX;
  232. chartY /= containerScaling.scaleY;
  233. }
  234. return extend(e, {
  235. chartX: Math.round(chartX),
  236. chartY: Math.round(chartY)
  237. });
  238. },
  239. /**
  240. * Get the click position in terms of axis values.
  241. *
  242. * @function Highcharts.Pointer#getCoordinates
  243. *
  244. * @param {Highcharts.PointerEventObject} e
  245. * Pointer event, extended with `chartX` and `chartY` properties.
  246. *
  247. * @return {Highcharts.PointerAxisCoordinatesObject}
  248. */
  249. getCoordinates: function (e) {
  250. var coordinates = {
  251. xAxis: [],
  252. yAxis: []
  253. };
  254. this.chart.axes.forEach(function (axis) {
  255. coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
  256. axis: axis,
  257. value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
  258. });
  259. });
  260. return coordinates;
  261. },
  262. /**
  263. * Finds the closest point to a set of coordinates, using the k-d-tree
  264. * algorithm.
  265. *
  266. * @function Highcharts.Pointer#findNearestKDPoints
  267. *
  268. * @param {Array<Highcharts.Series>} series
  269. * All the series to search in.
  270. *
  271. * @param {boolean|undefined} shared
  272. * Whether it is a shared tooltip or not.
  273. *
  274. * @param {Highcharts.PointerEventObject} e
  275. * The pointer event object, containing chart coordinates of the
  276. * pointer.
  277. *
  278. * @return {Highcharts.Point|undefined}
  279. * The point closest to given coordinates.
  280. */
  281. findNearestKDPoint: function (series, shared, e) {
  282. var closest, sort = function (p1, p2) {
  283. var isCloserX = p1.distX - p2.distX, isCloser = p1.dist - p2.dist, isAbove = (p2.series.group && p2.series.group.zIndex) -
  284. (p1.series.group && p1.series.group.zIndex), result;
  285. // We have two points which are not in the same place on xAxis
  286. // and shared tooltip:
  287. if (isCloserX !== 0 && shared) { // #5721
  288. result = isCloserX;
  289. // Points are not exactly in the same place on x/yAxis:
  290. }
  291. else if (isCloser !== 0) {
  292. result = isCloser;
  293. // The same xAxis and yAxis position, sort by z-index:
  294. }
  295. else if (isAbove !== 0) {
  296. result = isAbove;
  297. // The same zIndex, sort by array index:
  298. }
  299. else {
  300. result =
  301. p1.series.index > p2.series.index ?
  302. -1 :
  303. 1;
  304. }
  305. return result;
  306. };
  307. series.forEach(function (s) {
  308. var noSharedTooltip = s.noSharedTooltip && shared, compareX = (!noSharedTooltip &&
  309. s.options.findNearestPointBy.indexOf('y') < 0), point = s.searchPoint(e, compareX);
  310. if ( // Check that we actually found a point on the series.
  311. isObject(point, true) &&
  312. // Use the new point if it is closer.
  313. (!isObject(closest, true) ||
  314. (sort(closest, point) > 0))) {
  315. closest = point;
  316. }
  317. });
  318. return closest;
  319. },
  320. /**
  321. * @private
  322. * @function Highcharts.Pointer#getPointFromEvent
  323. *
  324. * @param {global.Event} e
  325. *
  326. * @return {Highcharts.Point|undefined}
  327. */
  328. getPointFromEvent: function (e) {
  329. var target = e.target, point;
  330. while (target && !point) {
  331. point = target.point;
  332. target = target.parentNode;
  333. }
  334. return point;
  335. },
  336. /**
  337. * @private
  338. * @function Highcharts.Pointer#getChartCoordinatesFromPoint
  339. * @param {Highcharts.Point} point
  340. * @param {boolean} [inverted]
  341. * @return {Highcharts.PointerCoordinatesObject|undefined}
  342. */
  343. getChartCoordinatesFromPoint: function (point, inverted) {
  344. var series = point.series, xAxis = series.xAxis, yAxis = series.yAxis, plotX = pick(point.clientX, point.plotX), shapeArgs = point.shapeArgs;
  345. if (xAxis && yAxis) {
  346. return inverted ? {
  347. chartX: xAxis.len + xAxis.pos - plotX,
  348. chartY: yAxis.len + yAxis.pos - point.plotY
  349. } : {
  350. chartX: plotX + xAxis.pos,
  351. chartY: point.plotY + yAxis.pos
  352. };
  353. }
  354. if (shapeArgs && shapeArgs.x && shapeArgs.y) {
  355. // E.g. pies do not have axes
  356. return {
  357. chartX: shapeArgs.x,
  358. chartY: shapeArgs.y
  359. };
  360. }
  361. },
  362. /**
  363. * Calculates what is the current hovered point/points and series.
  364. *
  365. * @private
  366. * @function Highcharts.Pointer#getHoverData
  367. *
  368. * @param {Highcharts.Point|undefined} existingHoverPoint
  369. * The point currrently beeing hovered.
  370. *
  371. * @param {Highcharts.Series|undefined} existingHoverSeries
  372. * The series currently beeing hovered.
  373. *
  374. * @param {Array<Highcharts.Series>} series
  375. * All the series in the chart.
  376. *
  377. * @param {boolean} isDirectTouch
  378. * Is the pointer directly hovering the point.
  379. *
  380. * @param {boolean|undefined} shared
  381. * Whether it is a shared tooltip or not.
  382. *
  383. * @param {Highcharts.PointerEventObject} [e]
  384. * The triggering event, containing chart coordinates of the pointer.
  385. *
  386. * @return {object}
  387. * Object containing resulting hover data: hoverPoint, hoverSeries,
  388. * and hoverPoints.
  389. */
  390. getHoverData: function (existingHoverPoint, existingHoverSeries, series, isDirectTouch, shared, e) {
  391. var hoverPoint, hoverPoints = [], hoverSeries = existingHoverSeries, useExisting = !!(isDirectTouch && existingHoverPoint), notSticky = hoverSeries && !hoverSeries.stickyTracking, filter = function (s) {
  392. return (s.visible &&
  393. !(!shared && s.directTouch) && // #3821
  394. pick(s.options.enableMouseTracking, true));
  395. },
  396. // Which series to look in for the hover point
  397. searchSeries = notSticky ?
  398. // Only search on hovered series if it has stickyTracking false
  399. [hoverSeries] :
  400. // Filter what series to look in.
  401. series.filter(function (s) {
  402. return filter(s) && s.stickyTracking;
  403. });
  404. // Use existing hovered point or find the one closest to coordinates.
  405. hoverPoint = useExisting || !e ?
  406. existingHoverPoint :
  407. this.findNearestKDPoint(searchSeries, shared, e);
  408. // Assign hover series
  409. hoverSeries = hoverPoint && hoverPoint.series;
  410. // If we have a hoverPoint, assign hoverPoints.
  411. if (hoverPoint) {
  412. // When tooltip is shared, it displays more than one point
  413. if (shared && !hoverSeries.noSharedTooltip) {
  414. searchSeries = series.filter(function (s) {
  415. return filter(s) && !s.noSharedTooltip;
  416. });
  417. // Get all points with the same x value as the hoverPoint
  418. searchSeries.forEach(function (s) {
  419. var point = find(s.points, function (p) {
  420. return p.x === hoverPoint.x && !p.isNull;
  421. });
  422. if (isObject(point)) {
  423. /*
  424. * Boost returns a minimal point. Convert it to a usable
  425. * point for tooltip and states.
  426. */
  427. if (s.chart.isBoosting) {
  428. point = s.getPoint(point);
  429. }
  430. hoverPoints.push(point);
  431. }
  432. });
  433. }
  434. else {
  435. hoverPoints.push(hoverPoint);
  436. }
  437. }
  438. return {
  439. hoverPoint: hoverPoint,
  440. hoverSeries: hoverSeries,
  441. hoverPoints: hoverPoints
  442. };
  443. },
  444. /**
  445. * With line type charts with a single tracker, get the point closest to the
  446. * mouse. Run Point.onMouseOver and display tooltip for the point or points.
  447. *
  448. * @private
  449. * @function Highcharts.Pointer#runPointActions
  450. *
  451. * @param {global.Event} e
  452. *
  453. * @param {Highcharts.PointerEventObject} [p]
  454. *
  455. * @return {void}
  456. *
  457. * @fires Highcharts.Point#event:mouseOut
  458. * @fires Highcharts.Point#event:mouseOver
  459. */
  460. runPointActions: function (e, p) {
  461. var pointer = this, chart = pointer.chart, series = chart.series, tooltip = (chart.tooltip && chart.tooltip.options.enabled ?
  462. chart.tooltip :
  463. void 0), shared = (tooltip ?
  464. tooltip.shared :
  465. false), hoverPoint = p || chart.hoverPoint, hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries,
  466. // onMouseOver or already hovering a series with directTouch
  467. isDirectTouch = (!e || e.type !== 'touchmove') && (!!p || ((hoverSeries && hoverSeries.directTouch) &&
  468. pointer.isDirectTouch)), hoverData = this.getHoverData(hoverPoint, hoverSeries, series, isDirectTouch, shared, e), useSharedTooltip, followPointer, anchor, points;
  469. // Update variables from hoverData.
  470. hoverPoint = hoverData.hoverPoint;
  471. points = hoverData.hoverPoints;
  472. hoverSeries = hoverData.hoverSeries;
  473. followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
  474. useSharedTooltip = (shared &&
  475. hoverSeries &&
  476. !hoverSeries.noSharedTooltip);
  477. // Refresh tooltip for kdpoint if new hover point or tooltip was hidden
  478. // #3926, #4200
  479. if (hoverPoint &&
  480. // !(hoverSeries && hoverSeries.directTouch) &&
  481. (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))) {
  482. (chart.hoverPoints || []).forEach(function (p) {
  483. if (points.indexOf(p) === -1) {
  484. p.setState();
  485. }
  486. });
  487. // Set normal state to previous series
  488. if (chart.hoverSeries !== hoverSeries) {
  489. hoverSeries.onMouseOver();
  490. }
  491. pointer.applyInactiveState(points);
  492. // Do mouseover on all points (#3919, #3985, #4410, #5622)
  493. (points || []).forEach(function (p) {
  494. p.setState('hover');
  495. });
  496. // If tracking is on series in stead of on each point,
  497. // fire mouseOver on hover point. // #4448
  498. if (chart.hoverPoint) {
  499. chart.hoverPoint.firePointEvent('mouseOut');
  500. }
  501. // Hover point may have been destroyed in the event handlers (#7127)
  502. if (!hoverPoint.series) {
  503. return;
  504. }
  505. hoverPoint.firePointEvent('mouseOver');
  506. /**
  507. * Contains all hovered points.
  508. *
  509. * @name Highcharts.Chart#hoverPoints
  510. * @type {Array<Highcharts.Point>|null}
  511. */
  512. chart.hoverPoints = points;
  513. /**
  514. * Contains the original hovered point.
  515. *
  516. * @name Highcharts.Chart#hoverPoint
  517. * @type {Highcharts.Point|null}
  518. */
  519. chart.hoverPoint = hoverPoint;
  520. // Draw tooltip if necessary
  521. if (tooltip) {
  522. tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
  523. }
  524. // Update positions (regardless of kdpoint or hoverPoint)
  525. }
  526. else if (followPointer && tooltip && !tooltip.isHidden) {
  527. anchor = tooltip.getAnchor([{}], e);
  528. tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
  529. }
  530. // Start the event listener to pick up the tooltip and crosshairs
  531. if (!pointer.unDocMouseMove) {
  532. pointer.unDocMouseMove = addEvent(chart.container.ownerDocument, 'mousemove', function (e) {
  533. var chart = charts[H.hoverChartIndex];
  534. if (chart) {
  535. chart.pointer.onDocumentMouseMove(e);
  536. }
  537. });
  538. }
  539. // Issues related to crosshair #4927, #5269 #5066, #5658
  540. chart.axes.forEach(function drawAxisCrosshair(axis) {
  541. var snap = pick(axis.crosshair.snap, true), point = !snap ?
  542. void 0 :
  543. H.find(points, function (p) {
  544. return p.series[axis.coll] === axis;
  545. });
  546. // Axis has snapping crosshairs, and one of the hover points belongs
  547. // to axis. Always call drawCrosshair when it is not snap.
  548. if (point || !snap) {
  549. axis.drawCrosshair(e, point);
  550. // Axis has snapping crosshairs, but no hover point belongs to axis
  551. }
  552. else {
  553. axis.hideCrosshair();
  554. }
  555. });
  556. },
  557. /**
  558. * Set inactive state to all series that are not currently hovered,
  559. * or, if `inactiveOtherPoints` is set to true, set inactive state to
  560. * all points within that series.
  561. *
  562. * @function Highcharts.Pointer#applyInactiveState
  563. *
  564. * @private
  565. *
  566. * @param {Array<Highcharts.Point>} points
  567. * Currently hovered points
  568. *
  569. */
  570. applyInactiveState: function (points) {
  571. var activeSeries = [], series;
  572. // Get all active series from the hovered points
  573. (points || []).forEach(function (item) {
  574. series = item.series;
  575. // Include itself
  576. activeSeries.push(series);
  577. // Include parent series
  578. if (series.linkedParent) {
  579. activeSeries.push(series.linkedParent);
  580. }
  581. // Include all child series
  582. if (series.linkedSeries) {
  583. activeSeries = activeSeries.concat(series.linkedSeries);
  584. }
  585. // Include navigator series
  586. if (series.navigatorSeries) {
  587. activeSeries.push(series.navigatorSeries);
  588. }
  589. });
  590. // Now loop over all series, filtering out active series
  591. this.chart.series.forEach(function (inactiveSeries) {
  592. if (activeSeries.indexOf(inactiveSeries) === -1) {
  593. // Inactive series
  594. inactiveSeries.setState('inactive', true);
  595. }
  596. else if (inactiveSeries.options.inactiveOtherPoints) {
  597. // Active series, but other points should be inactivated
  598. inactiveSeries.setAllPointsToState('inactive');
  599. }
  600. });
  601. },
  602. /**
  603. * Reset the tracking by hiding the tooltip, the hover series state and the
  604. * hover point
  605. *
  606. * @function Highcharts.Pointer#reset
  607. *
  608. * @param {boolean} [allowMove]
  609. * Instead of destroying the tooltip altogether, allow moving it if
  610. * possible.
  611. *
  612. * @param {number} [delay]
  613. *
  614. * @return {void}
  615. */
  616. reset: function (allowMove, delay) {
  617. var pointer = this, chart = pointer.chart, hoverSeries = chart.hoverSeries, hoverPoint = chart.hoverPoint, hoverPoints = chart.hoverPoints, tooltip = chart.tooltip, tooltipPoints = tooltip && tooltip.shared ?
  618. hoverPoints :
  619. hoverPoint;
  620. // Check if the points have moved outside the plot area (#1003, #4736,
  621. // #5101)
  622. if (allowMove && tooltipPoints) {
  623. splat(tooltipPoints).forEach(function (point) {
  624. if (point.series.isCartesian &&
  625. typeof point.plotX === 'undefined') {
  626. allowMove = false;
  627. }
  628. });
  629. }
  630. // Just move the tooltip, #349
  631. if (allowMove) {
  632. if (tooltip && tooltipPoints && splat(tooltipPoints).length) {
  633. tooltip.refresh(tooltipPoints);
  634. if (tooltip.shared && hoverPoints) { // #8284
  635. hoverPoints.forEach(function (point) {
  636. point.setState(point.state, true);
  637. if (point.series.isCartesian) {
  638. if (point.series.xAxis.crosshair) {
  639. point.series.xAxis
  640. .drawCrosshair(null, point);
  641. }
  642. if (point.series.yAxis.crosshair) {
  643. point.series.yAxis
  644. .drawCrosshair(null, point);
  645. }
  646. }
  647. });
  648. }
  649. else if (hoverPoint) { // #2500
  650. hoverPoint.setState(hoverPoint.state, true);
  651. chart.axes.forEach(function (axis) {
  652. if (axis.crosshair &&
  653. hoverPoint.series[axis.coll] === axis) {
  654. axis.drawCrosshair(null, hoverPoint);
  655. }
  656. });
  657. }
  658. }
  659. // Full reset
  660. }
  661. else {
  662. if (hoverPoint) {
  663. hoverPoint.onMouseOut();
  664. }
  665. if (hoverPoints) {
  666. hoverPoints.forEach(function (point) {
  667. point.setState();
  668. });
  669. }
  670. if (hoverSeries) {
  671. hoverSeries.onMouseOut();
  672. }
  673. if (tooltip) {
  674. tooltip.hide(delay);
  675. }
  676. if (pointer.unDocMouseMove) {
  677. pointer.unDocMouseMove = pointer.unDocMouseMove();
  678. }
  679. // Remove crosshairs
  680. chart.axes.forEach(function (axis) {
  681. axis.hideCrosshair();
  682. });
  683. pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null;
  684. }
  685. },
  686. /**
  687. * Scale series groups to a certain scale and translation.
  688. *
  689. * @private
  690. * @function Highcharts.Pointer#scaleGroups
  691. *
  692. * @param {Highcharts.SeriesPlotBoxObject} [attribs]
  693. *
  694. * @param {boolean} [clip]
  695. *
  696. * @return {void}
  697. */
  698. scaleGroups: function (attribs, clip) {
  699. var chart = this.chart, seriesAttribs;
  700. // Scale each series
  701. chart.series.forEach(function (series) {
  702. seriesAttribs = attribs || series.getPlotBox(); // #1701
  703. if (series.xAxis && series.xAxis.zoomEnabled && series.group) {
  704. series.group.attr(seriesAttribs);
  705. if (series.markerGroup) {
  706. series.markerGroup.attr(seriesAttribs);
  707. series.markerGroup.clip(clip ? chart.clipRect : null);
  708. }
  709. if (series.dataLabelsGroup) {
  710. series.dataLabelsGroup.attr(seriesAttribs);
  711. }
  712. }
  713. });
  714. // Clip
  715. chart.clipRect.attr(clip || chart.clipBox);
  716. },
  717. /**
  718. * Start a drag operation.
  719. *
  720. * @private
  721. * @function Highcharts.Pointer#dragStart
  722. *
  723. * @param {Highcharts.PointerEventObject} e
  724. *
  725. * @return {void}
  726. */
  727. dragStart: function (e) {
  728. var chart = this.chart;
  729. // Record the start position
  730. chart.mouseIsDown = e.type;
  731. chart.cancelClick = false;
  732. chart.mouseDownX = this.mouseDownX = e.chartX;
  733. chart.mouseDownY = this.mouseDownY = e.chartY;
  734. },
  735. /**
  736. * Perform a drag operation in response to a mousemove event while the mouse
  737. * is down.
  738. *
  739. * @private
  740. * @function Highcharts.Pointer#drag
  741. *
  742. * @param {Highcharts.PointerEventObject} e
  743. *
  744. * @return {void}
  745. */
  746. drag: function (e) {
  747. var chart = this.chart, chartOptions = chart.options.chart, chartX = e.chartX, chartY = e.chartY, zoomHor = this.zoomHor, zoomVert = this.zoomVert, plotLeft = chart.plotLeft, plotTop = chart.plotTop, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, clickedInside, size, selectionMarker = this.selectionMarker, mouseDownX = this.mouseDownX, mouseDownY = this.mouseDownY, panningEnabled = isObject(chartOptions.panning) ?
  748. chartOptions.panning && chartOptions.panning.enabled :
  749. chartOptions.panning, panKey = (chartOptions.panKey && e[chartOptions.panKey + 'Key']);
  750. // If the device supports both touch and mouse (like IE11), and we are
  751. // touch-dragging inside the plot area, don't handle the mouse event.
  752. // #4339.
  753. if (selectionMarker && selectionMarker.touch) {
  754. return;
  755. }
  756. // If the mouse is outside the plot area, adjust to cooordinates
  757. // inside to prevent the selection marker from going outside
  758. if (chartX < plotLeft) {
  759. chartX = plotLeft;
  760. }
  761. else if (chartX > plotLeft + plotWidth) {
  762. chartX = plotLeft + plotWidth;
  763. }
  764. if (chartY < plotTop) {
  765. chartY = plotTop;
  766. }
  767. else if (chartY > plotTop + plotHeight) {
  768. chartY = plotTop + plotHeight;
  769. }
  770. // determine if the mouse has moved more than 10px
  771. this.hasDragged = Math.sqrt(Math.pow(mouseDownX - chartX, 2) +
  772. Math.pow(mouseDownY - chartY, 2));
  773. if (this.hasDragged > 10) {
  774. clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
  775. // make a selection
  776. if (chart.hasCartesianSeries &&
  777. (this.zoomX || this.zoomY) &&
  778. clickedInside &&
  779. !panKey) {
  780. if (!selectionMarker) {
  781. this.selectionMarker = selectionMarker =
  782. chart.renderer.rect(plotLeft, plotTop, zoomHor ? 1 : plotWidth, zoomVert ? 1 : plotHeight, 0)
  783. .attr({
  784. 'class': 'highcharts-selection-marker',
  785. zIndex: 7
  786. })
  787. .add();
  788. if (!chart.styledMode) {
  789. selectionMarker.attr({
  790. fill: (chartOptions.selectionMarkerFill ||
  791. color('#335cad')
  792. .setOpacity(0.25).get())
  793. });
  794. }
  795. }
  796. }
  797. // adjust the width of the selection marker
  798. if (selectionMarker && zoomHor) {
  799. size = chartX - mouseDownX;
  800. selectionMarker.attr({
  801. width: Math.abs(size),
  802. x: (size > 0 ? 0 : size) + mouseDownX
  803. });
  804. }
  805. // adjust the height of the selection marker
  806. if (selectionMarker && zoomVert) {
  807. size = chartY - mouseDownY;
  808. selectionMarker.attr({
  809. height: Math.abs(size),
  810. y: (size > 0 ? 0 : size) + mouseDownY
  811. });
  812. }
  813. // panning
  814. if (clickedInside &&
  815. !selectionMarker &&
  816. panningEnabled) {
  817. chart.pan(e, chartOptions.panning);
  818. }
  819. }
  820. },
  821. /**
  822. * On mouse up or touch end across the entire document, drop the selection.
  823. *
  824. * @private
  825. * @function Highcharts.Pointer#drop
  826. *
  827. * @param {global.Event} e
  828. *
  829. * @return {void}
  830. */
  831. drop: function (e) {
  832. var pointer = this, chart = this.chart, hasPinched = this.hasPinched;
  833. if (this.selectionMarker) {
  834. var selectionData = {
  835. originalEvent: e,
  836. xAxis: [],
  837. yAxis: []
  838. }, selectionBox = this.selectionMarker, selectionLeft = selectionBox.attr ?
  839. selectionBox.attr('x') :
  840. selectionBox.x, selectionTop = selectionBox.attr ?
  841. selectionBox.attr('y') :
  842. selectionBox.y, selectionWidth = selectionBox.attr ?
  843. selectionBox.attr('width') :
  844. selectionBox.width, selectionHeight = selectionBox.attr ?
  845. selectionBox.attr('height') :
  846. selectionBox.height, runZoom;
  847. // a selection has been made
  848. if (this.hasDragged || hasPinched) {
  849. // record each axis' min and max
  850. chart.axes.forEach(function (axis) {
  851. if (axis.zoomEnabled &&
  852. defined(axis.min) &&
  853. (hasPinched ||
  854. pointer[{
  855. xAxis: 'zoomX',
  856. yAxis: 'zoomY'
  857. }[axis.coll]])) { // #859, #3569
  858. var horiz = axis.horiz, minPixelPadding = e.type === 'touchend' ?
  859. axis.minPixelPadding :
  860. 0, // #1207, #3075
  861. selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) +
  862. minPixelPadding), selectionMax = axis.toValue((horiz ?
  863. selectionLeft + selectionWidth :
  864. selectionTop + selectionHeight) - minPixelPadding);
  865. selectionData[axis.coll].push({
  866. axis: axis,
  867. // Min/max for reversed axes
  868. min: Math.min(selectionMin, selectionMax),
  869. max: Math.max(selectionMin, selectionMax)
  870. });
  871. runZoom = true;
  872. }
  873. });
  874. if (runZoom) {
  875. fireEvent(chart, 'selection', selectionData, function (args) {
  876. chart.zoom(extend(args, hasPinched ?
  877. { animation: false } :
  878. null));
  879. });
  880. }
  881. }
  882. if (isNumber(chart.index)) {
  883. this.selectionMarker = this.selectionMarker.destroy();
  884. }
  885. // Reset scaling preview
  886. if (hasPinched) {
  887. this.scaleGroups();
  888. }
  889. }
  890. // Reset all. Check isNumber because it may be destroyed on mouse up
  891. // (#877)
  892. if (chart && isNumber(chart.index)) {
  893. css(chart.container, { cursor: chart._cursor });
  894. chart.cancelClick = this.hasDragged > 10; // #370
  895. chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
  896. this.pinchDown = [];
  897. }
  898. },
  899. /**
  900. * @private
  901. * @function Highcharts.Pointer#onContainerMouseDown
  902. *
  903. * @param {Highcharts.PointerEventObject} e
  904. *
  905. * @return {void}
  906. */
  907. onContainerMouseDown: function (e) {
  908. // Normalize before the 'if' for the legacy IE (#7850)
  909. e = this.normalize(e);
  910. if (e.button !== 2) {
  911. this.zoomOption(e);
  912. // issue #295, dragging not always working in Firefox
  913. if (e.preventDefault) {
  914. e.preventDefault();
  915. }
  916. this.dragStart(e);
  917. }
  918. },
  919. /**
  920. * @private
  921. * @function Highcharts.Pointer#onDocumentMouseUp
  922. *
  923. * @param {Highcharts.PointerEventObject} e
  924. *
  925. * @return {void}
  926. */
  927. onDocumentMouseUp: function (e) {
  928. if (charts[H.hoverChartIndex]) {
  929. charts[H.hoverChartIndex].pointer.drop(e);
  930. }
  931. },
  932. /**
  933. * Special handler for mouse move that will hide the tooltip when the mouse
  934. * leaves the plotarea. Issue #149 workaround. The mouseleave event does not
  935. * always fire.
  936. *
  937. * @private
  938. * @function Highcharts.Pointer#onDocumentMouseMove
  939. *
  940. * @param {Highcharts.PointerEventObject} e
  941. *
  942. * @return {void}
  943. */
  944. onDocumentMouseMove: function (e) {
  945. var chart = this.chart, chartPosition = this.chartPosition;
  946. e = this.normalize(e, chartPosition);
  947. // If we're outside, hide the tooltip
  948. if (chartPosition &&
  949. !this.inClass(e.target, 'highcharts-tracker') &&
  950. !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
  951. this.reset();
  952. }
  953. },
  954. /**
  955. * When mouse leaves the container, hide the tooltip.
  956. *
  957. * @private
  958. * @function Highcharts.Pointer#onContainerMouseLeave
  959. *
  960. * @param {Highcharts.PointerEventObject} e
  961. *
  962. * @return {void}
  963. */
  964. onContainerMouseLeave: function (e) {
  965. var chart = charts[H.hoverChartIndex];
  966. // #4886, MS Touch end fires mouseleave but with no related target
  967. if (chart && (e.relatedTarget || e.toElement)) {
  968. chart.pointer.reset();
  969. // Also reset the chart position, used in #149 fix
  970. chart.pointer.chartPosition = void 0;
  971. }
  972. },
  973. /**
  974. * The mousemove, touchmove and touchstart event handler
  975. *
  976. * @private
  977. * @function Highcharts.Pointer#onContainerMouseMove
  978. *
  979. * @param {Highcharts.PointerEventObject} e
  980. *
  981. * @return {void}
  982. */
  983. onContainerMouseMove: function (e) {
  984. var chart = this.chart;
  985. if (!defined(H.hoverChartIndex) ||
  986. !charts[H.hoverChartIndex] ||
  987. !charts[H.hoverChartIndex].mouseIsDown) {
  988. H.hoverChartIndex = chart.index;
  989. }
  990. e = this.normalize(e);
  991. // In IE8 we apparently need this returnValue set to false in order to
  992. // avoid text being selected. But in Chrome, e.returnValue is prevented,
  993. // plus we don't need to run e.preventDefault to prevent selected text
  994. // in modern browsers. So we set it conditionally. Remove it when IE8 is
  995. // no longer needed. #2251, #3224.
  996. if (!e.preventDefault) {
  997. e.returnValue = false;
  998. }
  999. if (chart.mouseIsDown === 'mousedown') {
  1000. this.drag(e);
  1001. }
  1002. // Show the tooltip and run mouse over events (#977)
  1003. if ((this.inClass(e.target, 'highcharts-tracker') ||
  1004. chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) &&
  1005. !chart.openMenu) {
  1006. this.runPointActions(e);
  1007. }
  1008. },
  1009. /**
  1010. * Utility to detect whether an element has, or has a parent with, a
  1011. * specificclass name. Used on detection of tracker objects and on deciding
  1012. * whether hovering the tooltip should cause the active series to mouse out.
  1013. *
  1014. * @function Highcharts.Pointer#inClass
  1015. *
  1016. * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element
  1017. * The element to investigate.
  1018. *
  1019. * @param {string} className
  1020. * The class name to look for.
  1021. *
  1022. * @return {boolean|undefined}
  1023. * True if either the element or one of its parents has the given
  1024. * class name.
  1025. */
  1026. inClass: function (element, className) {
  1027. var elemClassName;
  1028. while (element) {
  1029. elemClassName = attr(element, 'class');
  1030. if (elemClassName) {
  1031. if (elemClassName.indexOf(className) !== -1) {
  1032. return true;
  1033. }
  1034. if (elemClassName.indexOf('highcharts-container') !== -1) {
  1035. return false;
  1036. }
  1037. }
  1038. element = element.parentNode;
  1039. }
  1040. },
  1041. /**
  1042. * @private
  1043. * @function Highcharts.Pointer#onTrackerMouseOut
  1044. *
  1045. * @param {Highcharts.PointerEventObject} e
  1046. *
  1047. * @return {void}
  1048. */
  1049. onTrackerMouseOut: function (e) {
  1050. var series = this.chart.hoverSeries, relatedTarget = e.relatedTarget || e.toElement;
  1051. this.isDirectTouch = false;
  1052. if (series &&
  1053. relatedTarget &&
  1054. !series.stickyTracking &&
  1055. !this.inClass(relatedTarget, 'highcharts-tooltip') &&
  1056. (!this.inClass(relatedTarget, 'highcharts-series-' + series.index) || // #2499, #4465, #5553
  1057. !this.inClass(relatedTarget, 'highcharts-tracker'))) {
  1058. series.onMouseOut();
  1059. }
  1060. },
  1061. /**
  1062. * @private
  1063. * @function Highcharts.Pointer#onContainerClick
  1064. *
  1065. * @param {Highcharts.PointerEventObject} e
  1066. *
  1067. * @return {void}
  1068. */
  1069. onContainerClick: function (e) {
  1070. var chart = this.chart, hoverPoint = chart.hoverPoint, plotLeft = chart.plotLeft, plotTop = chart.plotTop;
  1071. e = this.normalize(e);
  1072. if (!chart.cancelClick) {
  1073. // On tracker click, fire the series and point events. #783, #1583
  1074. if (hoverPoint &&
  1075. this.inClass(e.target, 'highcharts-tracker')) {
  1076. // the series click event
  1077. fireEvent(hoverPoint.series, 'click', extend(e, {
  1078. point: hoverPoint
  1079. }));
  1080. // the point click event
  1081. if (chart.hoverPoint) { // it may be destroyed (#1844)
  1082. hoverPoint.firePointEvent('click', e);
  1083. }
  1084. // When clicking outside a tracker, fire a chart event
  1085. }
  1086. else {
  1087. extend(e, this.getCoordinates(e));
  1088. // fire a click event in the chart
  1089. if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
  1090. fireEvent(chart, 'click', e);
  1091. }
  1092. }
  1093. }
  1094. },
  1095. /**
  1096. * Set the JS DOM events on the container and document. This method should
  1097. * contain a one-to-one assignment between methods and their handlers. Any
  1098. * advanced logic should be moved to the handler reflecting the event's
  1099. * name.
  1100. *
  1101. * @private
  1102. * @function Highcharts.Pointer#setDOMEvents
  1103. *
  1104. * @return {void}
  1105. */
  1106. setDOMEvents: function () {
  1107. var pointer = this, container = pointer.chart.container, ownerDoc = container.ownerDocument;
  1108. container.onmousedown = function (e) {
  1109. pointer.onContainerMouseDown(e);
  1110. };
  1111. container.onmousemove = function (e) {
  1112. pointer.onContainerMouseMove(e);
  1113. };
  1114. container.onclick = function (e) {
  1115. pointer.onContainerClick(e);
  1116. };
  1117. this.unbindContainerMouseLeave = addEvent(container, 'mouseleave', pointer.onContainerMouseLeave);
  1118. if (!H.unbindDocumentMouseUp) {
  1119. H.unbindDocumentMouseUp = addEvent(ownerDoc, 'mouseup', pointer.onDocumentMouseUp);
  1120. }
  1121. if (H.hasTouch) {
  1122. addEvent(container, 'touchstart', function (e) {
  1123. pointer.onContainerTouchStart(e);
  1124. });
  1125. addEvent(container, 'touchmove', function (e) {
  1126. pointer.onContainerTouchMove(e);
  1127. });
  1128. if (!H.unbindDocumentTouchEnd) {
  1129. H.unbindDocumentTouchEnd = addEvent(ownerDoc, 'touchend', pointer.onDocumentTouchEnd);
  1130. }
  1131. }
  1132. },
  1133. /**
  1134. * Destroys the Pointer object and disconnects DOM events.
  1135. *
  1136. * @function Highcharts.Pointer#destroy
  1137. *
  1138. * @return {void}
  1139. */
  1140. destroy: function () {
  1141. var pointer = this;
  1142. if (pointer.unDocMouseMove) {
  1143. pointer.unDocMouseMove();
  1144. }
  1145. this.unbindContainerMouseLeave();
  1146. if (!H.chartCount) {
  1147. if (H.unbindDocumentMouseUp) {
  1148. H.unbindDocumentMouseUp = H.unbindDocumentMouseUp();
  1149. }
  1150. if (H.unbindDocumentTouchEnd) {
  1151. H.unbindDocumentTouchEnd = H.unbindDocumentTouchEnd();
  1152. }
  1153. }
  1154. // memory and CPU leak
  1155. clearInterval(pointer.tooltipTimeout);
  1156. objectEach(pointer, function (val, prop) {
  1157. pointer[prop] = null;
  1158. });
  1159. }
  1160. };