123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- /**
- * (c) 2010-2019 Torstein Honsi
- *
- * License: www.highcharts.com/license
- */
- 'use strict';
- import H from './Globals.js';
- import './Utilities.js';
- var charts = H.charts,
- extend = H.extend,
- noop = H.noop,
- pick = H.pick,
- Pointer = H.Pointer;
- // Support for touch devices
- extend(Pointer.prototype, /** @lends Pointer.prototype */ {
- /**
- * Run translation operations
- *
- * @private
- * @function Highcharts.Pointer#pinchTranslate
- *
- * @param {Array<*>} pinchDown
- *
- * @param {Array<*>} touches
- *
- * @param {*} transform
- *
- * @param {*} selectionMarker
- *
- * @param {*} clip
- *
- * @param {*} lastValidTouch
- */
- pinchTranslate: function (
- pinchDown,
- touches,
- transform,
- selectionMarker,
- clip,
- lastValidTouch
- ) {
- if (this.zoomHor) {
- this.pinchTranslateDirection(
- true,
- pinchDown,
- touches,
- transform,
- selectionMarker,
- clip,
- lastValidTouch
- );
- }
- if (this.zoomVert) {
- this.pinchTranslateDirection(
- false,
- pinchDown,
- touches,
- transform,
- selectionMarker,
- clip,
- lastValidTouch
- );
- }
- },
- /**
- * Run translation operations for each direction (horizontal and vertical)
- * independently.
- *
- * @private
- * @function Highcharts.Pointer#pinchTranslateDirection
- *
- * @param {boolean} horiz
- *
- * @param {Array<*>} pinchDown
- *
- * @param {Array<*>} touches
- *
- * @param {*} transform
- *
- * @param {*} selectionMarker
- *
- * @param {*} clip
- *
- * @param {*} lastValidTouch
- *
- * @param {number|undefined} [forcedScale=1]
- */
- pinchTranslateDirection: function (
- horiz,
- pinchDown,
- touches,
- transform,
- selectionMarker,
- clip,
- lastValidTouch,
- forcedScale
- ) {
- var chart = this.chart,
- xy = horiz ? 'x' : 'y',
- XY = horiz ? 'X' : 'Y',
- sChartXY = 'chart' + XY,
- wh = horiz ? 'width' : 'height',
- plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
- selectionWH,
- selectionXY,
- clipXY,
- scale = forcedScale || 1,
- inverted = chart.inverted,
- bounds = chart.bounds[horiz ? 'h' : 'v'],
- singleTouch = pinchDown.length === 1,
- touch0Start = pinchDown[0][sChartXY],
- touch0Now = touches[0][sChartXY],
- touch1Start = !singleTouch && pinchDown[1][sChartXY],
- touch1Now = !singleTouch && touches[1][sChartXY],
- outOfBounds,
- transformScale,
- scaleKey,
- setScale = function () {
- // Don't zoom if fingers are too close on this axis
- if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
- scale = forcedScale ||
- Math.abs(touch0Now - touch1Now) /
- Math.abs(touch0Start - touch1Start);
- }
- clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
- selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] /
- scale;
- };
- // Set the scale, first pass
- setScale();
- // The clip position (x or y) is altered if out of bounds, the selection
- // position is not
- selectionXY = clipXY;
- // Out of bounds
- if (selectionXY < bounds.min) {
- selectionXY = bounds.min;
- outOfBounds = true;
- } else if (selectionXY + selectionWH > bounds.max) {
- selectionXY = bounds.max - selectionWH;
- outOfBounds = true;
- }
- // Is the chart dragged off its bounds, determined by dataMin and
- // dataMax?
- if (outOfBounds) {
- // Modify the touchNow position in order to create an elastic drag
- // movement. This indicates to the user that the chart is responsive
- // but can't be dragged further.
- touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
- if (!singleTouch) {
- touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
- }
- // Set the scale, second pass to adapt to the modified touchNow
- // positions
- setScale();
- } else {
- lastValidTouch[xy] = [touch0Now, touch1Now];
- }
- // Set geometry for clipping, selection and transformation
- if (!inverted) {
- clip[xy] = clipXY - plotLeftTop;
- clip[wh] = selectionWH;
- }
- scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
- transformScale = inverted ? 1 / scale : scale;
- selectionMarker[wh] = selectionWH;
- selectionMarker[xy] = selectionXY;
- transform[scaleKey] = scale;
- transform['translate' + XY] = (transformScale * plotLeftTop) +
- (touch0Now - (transformScale * touch0Start));
- },
- /**
- * Handle touch events with two touches
- *
- * @private
- * @function Highcharts.Pointer#pinch
- *
- * @param {Highcharts.PointerEvent} e
- */
- pinch: function (e) {
- var self = this,
- chart = self.chart,
- pinchDown = self.pinchDown,
- touches = e.touches,
- touchesLength = touches.length,
- lastValidTouch = self.lastValidTouch,
- hasZoom = self.hasZoom,
- selectionMarker = self.selectionMarker,
- transform = {},
- fireClickEvent = touchesLength === 1 && (
- (
- self.inClass(e.target, 'highcharts-tracker') &&
- chart.runTrackerClick
- ) ||
- self.runChartClick
- ),
- clip = {};
- // Don't initiate panning until the user has pinched. This prevents us
- // from blocking page scrolling as users scroll down a long page
- // (#4210).
- if (touchesLength > 1) {
- self.initiated = true;
- }
- // On touch devices, only proceed to trigger click if a handler is
- // defined
- if (hasZoom && self.initiated && !fireClickEvent) {
- e.preventDefault();
- }
- // Normalize each touch
- [].map.call(touches, function (e) {
- return self.normalize(e);
- });
- // Register the touch start position
- if (e.type === 'touchstart') {
- [].forEach.call(touches, function (e, i) {
- pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
- });
- lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] &&
- pinchDown[1].chartX];
- lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] &&
- pinchDown[1].chartY];
- // Identify the data bounds in pixels
- chart.axes.forEach(function (axis) {
- if (axis.zoomEnabled) {
- var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
- minPixelPadding = axis.minPixelPadding,
- min = axis.toPixels(
- pick(axis.options.min, axis.dataMin)
- ),
- max = axis.toPixels(
- pick(axis.options.max, axis.dataMax)
- ),
- absMin = Math.min(min, max),
- absMax = Math.max(min, max);
- // Store the bounds for use in the touchmove handler
- bounds.min = Math.min(axis.pos, absMin - minPixelPadding);
- bounds.max = Math.max(
- axis.pos + axis.len,
- absMax + minPixelPadding
- );
- }
- });
- self.res = true; // reset on next move
- // Optionally move the tooltip on touchmove
- } else if (self.followTouchMove && touchesLength === 1) {
- this.runPointActions(self.normalize(e));
- // Event type is touchmove, handle panning and pinching
- } else if (pinchDown.length) { // can be 0 when releasing, if touchend
- // fires first
- // Set the marker
- if (!selectionMarker) {
- self.selectionMarker = selectionMarker = extend({
- destroy: noop,
- touch: true
- }, chart.plotBox);
- }
- self.pinchTranslate(
- pinchDown,
- touches,
- transform,
- selectionMarker,
- clip,
- lastValidTouch
- );
- self.hasPinched = hasZoom;
- // Scale and translate the groups to provide visual feedback during
- // pinching
- self.scaleGroups(transform, clip);
- if (self.res) {
- self.res = false;
- this.reset(false, 0);
- }
- }
- },
- /**
- * General touch handler shared by touchstart and touchmove.
- *
- * @private
- * @function Highcharts.Pointer#touch
- *
- * @param {Highcharts.PointerEvent} e
- *
- * @param {boolean} start
- */
- touch: function (e, start) {
- var chart = this.chart,
- hasMoved,
- pinchDown,
- isInside;
- if (chart.index !== H.hoverChartIndex) {
- this.onContainerMouseLeave({ relatedTarget: true });
- }
- H.hoverChartIndex = chart.index;
- if (e.touches.length === 1) {
- e = this.normalize(e);
- isInside = chart.isInsidePlot(
- e.chartX - chart.plotLeft,
- e.chartY - chart.plotTop
- );
- if (isInside && !chart.openMenu) {
- // Run mouse events and display tooltip etc
- if (start) {
- this.runPointActions(e);
- }
- // Android fires touchmove events after the touchstart even if
- // the finger hasn't moved, or moved only a pixel or two. In iOS
- // however, the touchmove doesn't fire unless the finger moves
- // more than ~4px. So we emulate this behaviour in Android by
- // checking how much it moved, and cancelling on small
- // distances. #3450.
- if (e.type === 'touchmove') {
- pinchDown = this.pinchDown;
- hasMoved = pinchDown[0] ? Math.sqrt( // #5266
- Math.pow(pinchDown[0].chartX - e.chartX, 2) +
- Math.pow(pinchDown[0].chartY - e.chartY, 2)
- ) >= 4 : false;
- }
- if (pick(hasMoved, true)) {
- this.pinch(e);
- }
- } else if (start) {
- // Hide the tooltip on touching outside the plot area (#1203)
- this.reset();
- }
- } else if (e.touches.length === 2) {
- this.pinch(e);
- }
- },
- /**
- * @private
- * @function Highcharts.Pointer#onContainerTouchStart
- *
- * @param {Highcharts.PointerEvent} e
- */
- onContainerTouchStart: function (e) {
- this.zoomOption(e);
- this.touch(e, true);
- },
- /**
- * @private
- * @function Highcharts.Pointer#onContainerTouchMove
- *
- * @param {Highcharts.PointerEvent} e
- */
- onContainerTouchMove: function (e) {
- this.touch(e);
- },
- /**
- * @private
- * @function Highcharts.Pointer#onDocumentTouchEnd
- *
- * @param {Highcharts.PointerEvent} e
- */
- onDocumentTouchEnd: function (e) {
- if (charts[H.hoverChartIndex]) {
- charts[H.hoverChartIndex].pointer.drop(e);
- }
- }
- });
|