Interaction.js 46 KB

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