'use strict' ; import type Axis from './Axis/Axis' ; import type Chart from './Chart/Chart' ; import type { DOMElementType } from './Renderer/DOMElementType' ; import type NodesComposition from '../Series/NodesComposition' ; import type Options from './Options' ; import type Point from './Series/Point' ; import type PointerEvent from './PointerEvent' ; import type Series from './Series/Series' ; import type SVGElement from './Renderer/SVG/SVGElement' ; import Color from './Color/Color.js' ; const { parse : color } = Color ; import H from './Globals.js' ; const { charts , noop } = H ; import { Palette } from '../Core/Color/Palettes.js' ; import Tooltip from './Tooltip.js' ; import U from './Utilities.js' ; const { addEvent , attr , css , defined , extend , find , fireEvent , isNumber , isObject , objectEach , offset , pick , splat } = U ; declare module './Chart/ChartLike' { interface ChartLike { cancelClick ? : boolean ; hoverPoint ? : Point ; hoverPoints ? : Array < Point > ; hoverSeries ? : Series ; mouseDownX ? : number ; mouseDownY ? : number ; mouseIsDown ? : ( boolean | string ) ; tooltip ? : Tooltip ; } } /**
 * The mouse and touch tracker object. Each  Chart item has one
 * assosiated Pointer item that can be accessed from the   Chart . pointer * property.
 *
 *  class name param { Highcharts . Chart } chart param { Highcharts . Options } options class Pointer { public static hoverChartIndex : ( number | undefined ) ; public static unbindDocumentMouseUp : ( Function | undefined ) ; public static unbindDocumentTouchEnd : ( Function | undefined ) ; public constructor ( chart , options ) { this . chart = chart ; this . hasDragged = false ; this . options = options ; this . init ( chart , options ) ; } public chart : Chart ; public chartPosition ? : Pointer . ChartPositionObject ; public followTouchMove ? : boolean ; public hasDragged : ( false | number ) ; public hasPinched ? : boolean ; public hasZoom ? : boolean ; public hoverX ? : ( Point | Array < Point > ) ; public initiated ? : boolean ; public isDirectTouch ? : boolean ; public lastValidTouch : object = { } ; public mouseDownX ? : number ; public mouseDownY ? : number ; public options : Options ; public pinchDown : Array < any > = [ ] ; public res ? : boolean ; public runChartClick : boolean = false ; public selectionMarker ? : SVGElement ; public tooltipTimeout ? : number ; public eventsToUnbind : Array < Function > = [ ] ; public unDocMouseMove ? : Function ; public zoomHor ? : boolean ; public zoomVert ? : boolean ; public zoomX ? : boolean ; public zoomY ? : boolean ; private function param { Array < Highcharts . Point > } points public applyInactiveState ( points ) { let activeSeries = [ ] as Array < Series > , series ; ( points || [ ] ) . forEach ( function ( item ) : void { series = item . series ; activeSeries . push ( series ) ; if ( series . linkedParent ) { activeSeries . push ( series . linkedParent ) ; } if ( series . linkedSeries ) { activeSeries = activeSeries . concat ( series . linkedSeries ) ; } if ( series . navigatorSeries ) { activeSeries . push ( series . navigatorSeries ) ; } } ) ; this . chart . series . forEach ( function ( inactiveSeries ) : void { if ( activeSeries . indexOf ( inactiveSeries ) === - 1 ) { inactiveSeries . setState ( 'inactive' , true ) ; } else if ( inactiveSeries . options . inactiveOtherPoints ) { inactiveSeries . setAllPointsToState ( 'inactive' ) ; } } ) ; } function public destroy ( ) { const pointer = this ; this . eventsToUnbind . forEach ( ( unbind ) : void => unbind ( ) ) ; this . eventsToUnbind = [ ] ; if ( ! H . chartCount ) { if ( Pointer . unbindDocumentMouseUp ) { Pointer . unbindDocumentMouseUp = Pointer . unbindDocumentMouseUp ( ) ; } if ( Pointer . unbindDocumentTouchEnd ) { Pointer . unbindDocumentTouchEnd = ( Pointer . unbindDocumentTouchEnd ( ) ) ; } } clearInterval ( pointer . tooltipTimeout ) ; objectEach ( pointer , function ( _val , prop ) : void { pointer [ prop ] = void 0 as any ; } ) ; } private function public drag ( e ) { const chart = this . chart , chartOptions = chart . options . chart , zoomHor = this . zoomHor , zoomVert = this . zoomVert , plotLeft = chart . plotLeft , plotTop = chart . plotTop , plotWidth = chart . plotWidth , plotHeight = chart . plotHeight , mouseDownX = ( this . mouseDownX || 0 ) , mouseDownY = ( this . mouseDownY || 0 ) , panningEnabled = isObject ( chartOptions . panning ) ? chartOptions . panning && chartOptions . panning . enabled : chartOptions . panning , panKey = ( chartOptions . panKey && ( e as any ) [ chartOptions . panKey + 'Key' ] ) ; let chartX = e . chartX , chartY = e . chartY , clickedInside , size , selectionMarker = this . selectionMarker ; if ( selectionMarker && ( selectionMarker as any ) . touch ) { return ; } if ( chartX < plotLeft ) { chartX = plotLeft ; } else if ( chartX > plotLeft + plotWidth ) { chartX = plotLeft + plotWidth ; } if ( chartY < plotTop ) { chartY = plotTop ; } else if ( chartY > plotTop + plotHeight ) { chartY = plotTop + plotHeight ; } this . hasDragged = Math . sqrt ( Math . pow ( mouseDownX - chartX , 2 ) + Math . pow ( mouseDownY - chartY , 2 ) ) ; if ( this . hasDragged > 10 ) { clickedInside = chart . isInsidePlot ( mouseDownX - plotLeft , mouseDownY - plotTop , { visiblePlotOnly : true } ) ; if ( ( chart . hasCartesianSeries || chart . mapView ) && ( this . zoomX || this . zoomY ) && clickedInside && ! panKey ) { if ( ! selectionMarker ) { this . selectionMarker = selectionMarker = chart . renderer . rect ( plotLeft , plotTop , zoomHor ? 1 : plotWidth , zoomVert ? 1 : plotHeight , 0 ) . attr ( { 'class' : 'highcharts-selection-marker' , zIndex : 7 } ) . add ( ) ; if ( ! chart . styledMode ) { selectionMarker . attr ( { fill : ( chartOptions . selectionMarkerFill || color ( Palette . highlightColor80 ) . setOpacity ( 0.25 ) . get ( ) ) } ) ; } } } if ( selectionMarker && zoomHor ) { size = chartX - mouseDownX ; selectionMarker . attr ( { width : Math . abs ( size ) , x : ( size > 0 ? 0 : size ) + mouseDownX } ) ; } if ( selectionMarker && zoomVert ) { size = chartY - mouseDownY ; selectionMarker . attr ( { height : Math . abs ( size ) , y : ( size > 0 ? 0 : size ) + mouseDownY } ) ; } if ( clickedInside && ! selectionMarker && panningEnabled ) { ( chart . pan as any ) ( e , chartOptions . panning ) ; } } } private function public dragStart ( e ) { const chart = this . chart ; chart . mouseIsDown = e . type ; chart . cancelClick = false ; chart . mouseDownX = this . mouseDownX = e . chartX ; chart . mouseDownY = this . mouseDownY = e . chartY ; } private function public drop ( e ) { const pointer = this , chart = this . chart , hasPinched = this . hasPinched ; if ( this . selectionMarker ) { let selectionBox = this . selectionMarker , x = selectionBox . attr ? selectionBox . attr ( 'x' ) : selectionBox . x , y = selectionBox . attr ? selectionBox . attr ( 'y' ) : selectionBox . y , width = selectionBox . attr ? selectionBox . attr ( 'width' ) : selectionBox . width , height = selectionBox . attr ? selectionBox . attr ( 'height' ) : selectionBox . height , selectionData = { originalEvent : e , xAxis : [ ] , yAxis : [ ] , x , y , width , height } , runZoom = Boolean ( chart . mapView ) ; if ( this . hasDragged || hasPinched ) { chart . axes . forEach ( function ( axis ) : void { if ( axis . zoomEnabled && defined ( axis . min ) && ( hasPinched || pointer [ ( { xAxis : 'zoomX' , yAxis : 'zoomY' } as Record < string , ( 'zoomX' | 'zoomY' ) > ) [ axis . coll ] ] ) && isNumber ( x ) && isNumber ( y ) ) { const horiz = axis . horiz , minPixelPadding = e . type === 'touchend' ? axis . minPixelPadding : 0 , selectionMin = axis . toValue ( ( horiz ? x : y ) + minPixelPadding ) , selectionMax = axis . toValue ( ( horiz ? x + width : y + height ) - minPixelPadding ) ; ( selectionData as any ) [ axis . coll ] . push ( { axis : axis , min : Math . min ( selectionMin , selectionMax ) , max : Math . max ( selectionMin , selectionMax ) } ) ; runZoom = true ; } } ) ; if ( runZoom ) { fireEvent ( chart , 'selection' , selectionData , function ( args ) : void { ( chart . zoom as any ) ( extend ( args , hasPinched ? { animation : false } : null as any ) ) ; } ) ; } } if ( isNumber ( chart . index ) ) { this . selectionMarker = this . selectionMarker . destroy ( ) ; } if ( hasPinched ) { this . scaleGroups ( ) ; } } if ( chart && isNumber ( chart . index ) ) { css ( chart . container , { cursor : chart . _cursor as any } ) ; chart . cancelClick = this . hasDragged > 10 ; chart . mouseIsDown = this . hasDragged = this . hasPinched = false ; this . pinchDown = [ ] ; } } function param { Array < Highcharts . Series > } series param { boolean | undefined } shared param { Highcharts . PointerEventObject } e return { Highcharts . Point | undefined } public findNearestKDPoint ( series , shared , e ) { const chart = this . chart ; const hoverPoint = chart . hoverPoint ; const tooltip = chart . tooltip ; if ( hoverPoint && tooltip && tooltip . isStickyOnContact ( ) ) { return hoverPoint ; } let closest ; private function sort ( p1 , p2 ) { const isCloserX = ( p1 . distX as any ) - ( p2 . distX as any ) , isCloser = ( p1 . dist as any ) - ( p2 . dist as any ) , isAbove = ( ( p2 . series . group && p2 . series . group . zIndex ) - ( p1 . series . group && p1 . series . group . zIndex ) ) ; let result ; if ( isCloserX !== 0 && shared ) { result = isCloserX ; } else if ( isCloser !== 0 ) { result = isCloser ; } else if ( isAbove !== 0 ) { result = isAbove ; } else { result = ( p1 . series . index as any ) > ( p2 . series . index as any ) ? - 1 : 1 ; } return result ; } series . forEach ( function ( s ) : void { const noSharedTooltip = s . noSharedTooltip && shared , compareX = ( ! noSharedTooltip && ( s . options . findNearestPointBy as any ) . indexOf ( 'y' ) < 0 ) , point = s . searchPoint ( e , compareX ) ; if ( isObject ( point , true ) && point . series && ( ! isObject ( closest , true ) || ( sort ( closest as any , point as any ) > 0 ) ) ) { closest = point ; } } ) ; return closest ; } private function public getChartCoordinatesFromPoint ( point , inverted ? ) { const series = point . series , xAxis = series . xAxis , yAxis = series . yAxis , shapeArgs = point . shapeArgs ; if ( xAxis && yAxis ) { let x = pick < number | undefined , number > ( point . clientX , point . plotX as any ) ; let y = point . plotY || 0 ; if ( ( point as NodesComposition . PointComposition ) . isNode && shapeArgs && isNumber ( shapeArgs . x ) && isNumber ( shapeArgs . y ) ) { x = shapeArgs . x ; y = shapeArgs . y ; } return inverted ? { chartX : yAxis . len + yAxis . pos - y , chartY : xAxis . len + xAxis . pos - x } : { chartX : x + xAxis . pos , chartY : y + yAxis . pos } ; } if ( shapeArgs && shapeArgs . x && shapeArgs . y ) { return { chartX : shapeArgs . x , chartY : shapeArgs . y } ; } } function return { Highcharts . ChartPositionObject } public getChartPosition ( ) { if ( this . chartPosition ) { return this . chartPosition ; } const { container } = this . chart ; const pos = offset ( container ) ; this . chartPosition = { left : pos . left , top : pos . top , scaleX : 1 , scaleY : 1 } ; const offsetWidth = container . offsetWidth ; const offsetHeight = container . offsetHeight ; if ( offsetWidth > 2 && offsetHeight > 2 ) { this . chartPosition . scaleX = pos . width / offsetWidth ; this . chartPosition . scaleY = pos . height / offsetHeight ; } return this . chartPosition ; } function param { Highcharts . PointerEventObject } e return { Highcharts . PointerAxisCoordinatesObject } public getCoordinates ( e ) { const coordinates = { xAxis : [ ] , yAxis : [ ] } ; this . chart . axes . forEach ( function ( axis ) : void { ( coordinates as any ) [ axis . isXAxis ? 'xAxis' : 'yAxis' ] . push ( { axis : axis , value : axis . toValue ( e [ axis . horiz ? 'chartX' : 'chartY' ] ) } ) ; } ) ; return coordinates ; } private function param { Highcharts . Point | undefined } existingHoverPoint param { Highcharts . Series | undefined } existingHoverSeries param { Array < Highcharts . Series > } series param { boolean } isDirectTouch param { boolean | undefined } shared param { Highcharts . PointerEventObject } e return { Object } public getHoverData ( existingHoverPoint , existingHoverSeries , series , isDirectTouch ? , shared ? , e ? ) { const hoverPoints = [ ] as Array < Point > , useExisting = ! ! ( isDirectTouch && existingHoverPoint ) , filter = function ( s ) : boolean { return ( s . visible && ! ( ! shared && s . directTouch ) && pick ( s . options . enableMouseTracking , true ) ) ; } ; let hoverSeries = existingHoverSeries as any , searchSeries , eventArgs = { chartX : e ? e . chartX : void 0 , chartY : e ? e . chartY : void 0 , shared : shared } ; fireEvent ( this , 'beforeGetHoverData' , eventArgs ) ; const notSticky = hoverSeries && ! hoverSeries . stickyTracking ; searchSeries = notSticky ? [ hoverSeries as any ] : series . filter ( function ( s ) : boolean { return eventArgs . filter ? eventArgs . filter ( s ) : filter ( s ) && s . stickyTracking ; } ) ; const hoverPoint = useExisting || ! e ? existingHoverPoint : this . findNearestKDPoint ( searchSeries , shared , e ) as any ; hoverSeries = hoverPoint && hoverPoint . series ; if ( hoverPoint ) { if ( shared && ! hoverSeries . noSharedTooltip ) { searchSeries = series . filter ( function ( s ) : boolean { return eventArgs . filter ? eventArgs . filter ( s ) : filter ( s ) && ! s . noSharedTooltip ; } ) ; searchSeries . forEach ( function ( s ) : any { let point = find ( s . points , function ( p ) : boolean { return p . x === hoverPoint . x && ! p . isNull ; } ) ; if ( isObject ( point ) ) { if ( s . chart . isBoosting ) { point = s . getPoint ( point ) ; } hoverPoints . push ( point as Point ) ; } } ) ; } else { hoverPoints . push ( hoverPoint ) ; } } eventArgs = { hoverPoint : hoverPoint } ; fireEvent ( this , 'afterGetHoverData' , eventArgs ) ; return { hoverPoint : eventArgs . hoverPoint , hoverSeries : hoverSeries , hoverPoints : hoverPoints } ; } private function public getPointFromEvent ( e ) { let target = e . target , point ; while ( target && ! point ) { point = ( target as any ) . point ; target = ( target as any ) . parentNode ; } return point ; } private function public onTrackerMouseOut ( e ) { const chart = this . chart ; const relatedTarget = e . relatedTarget || e . toElement ; const series = chart . hoverSeries ; this . isDirectTouch = false ; if ( series && relatedTarget && ! series . stickyTracking && ! this . inClass ( relatedTarget as any , 'highcharts-tooltip' ) && ( ! this . inClass ( relatedTarget as any , 'highcharts-series-' + series . index ) || ! this . inClass ( relatedTarget as any , 'highcharts-tracker' ) ) ) { series . onMouseOut ( ) ; } } function param { Highcharts . SVGDOMElement | Highcharts . HTMLDOMElement } element param { string } className return { boolean | undefined } public inClass ( element , className ) { let elemClassName ; while ( element ) { elemClassName = attr ( element , 'class' ) ; if ( elemClassName ) { if ( elemClassName . indexOf ( className ) !== - 1 ) { return true ; } if ( elemClassName . indexOf ( 'highcharts-container' ) !== - 1 ) { return false ; } } element = element . parentNode as any ; } } private function param { Highcharts . Chart } chart param { Highcharts . Options } options public init ( chart , options ) { this . options = options ; this . chart = chart ; this . runChartClick = Boolean ( options . chart . events && options . chart . events . click ) ; this . pinchDown = [ ] ; this . lastValidTouch = { } ; if ( Tooltip ) { name type { Highcharts . Tooltip } chart . tooltip = new Tooltip ( chart , options . tooltip as any ) ; this . followTouchMove = pick ( ( options . tooltip as any ) . followTouchMove , true ) ; } this . setDOMEvents ( ) ; } function param { global . MouseEvent | global . PointerEvent | global . TouchEvent } e param { Highcharts . OffsetObject } chartPosition return { Highcharts . PointerEventObject } public normalize < T extends PointerEvent > ( e , chartPosition ? ) { const touches = ( e as TouchEvent ) . touches ; const ePos = ( touches ? touches . length ? touches . item ( 0 ) as Touch : ( pick ( touches . changedTouches , ( e as TouchEvent ) . changedTouches ) ) [ 0 ] : e as unknown as PointerEvent ) ; if ( ! chartPosition ) { chartPosition = this . getChartPosition ( ) ; } let chartX = ePos . pageX - chartPosition . left , chartY = ePos . pageY - chartPosition . top ; chartX /= chartPosition . scaleX ; chartY /= chartPosition . scaleY ; return extend ( e , { chartX : Math . round ( chartX ) , chartY : Math . round ( chartY ) } ) as any ; } private function public onContainerClick ( e ) { const chart = this . chart ; const hoverPoint = chart . hoverPoint ; const pEvt = this . normalize ( e ) ; const plotLeft = chart . plotLeft ; const plotTop = chart . plotTop ; if ( ! chart . cancelClick ) { if ( hoverPoint && this . inClass ( pEvt . target as any , 'highcharts-tracker' ) ) { fireEvent ( hoverPoint . series , 'click' , extend ( pEvt , { point : hoverPoint } ) ) ; if ( chart . hoverPoint ) { hoverPoint . firePointEvent ( 'click' , pEvt ) ; } } else { extend ( pEvt , this . getCoordinates ( pEvt ) ) ; if ( chart . isInsidePlot ( pEvt . chartX - plotLeft , pEvt . chartY - plotTop , { visiblePlotOnly : true } ) ) { fireEvent ( chart , 'click' , pEvt ) ; } } } } private function public onContainerMouseDown ( e ) { const isPrimaryButton = ( ( e . buttons || e . button ) & 1 ) === 1 ; e = this . normalize ( e ) ; if ( H . isFirefox && e . button !== 0 ) { this . onContainerMouseMove ( e ) ; } if ( typeof e . button === 'undefined' || isPrimaryButton ) { this . zoomOption ( e ) ; if ( isPrimaryButton && e . preventDefault ) { e . preventDefault ( ) ; } this . dragStart ( e as PointerEvent ) ; } } private function public onContainerMouseLeave ( e ) { const chart = charts [ pick ( Pointer . hoverChartIndex , - 1 ) ] ; const tooltip = this . chart . tooltip ; if ( tooltip && tooltip . shouldStickOnContact ( ) && this . inClass ( e . relatedTarget as any , 'highcharts-tooltip-container' ) ) { return ; } e = this . normalize ( e ) ; if ( chart && ( e . relatedTarget || ( e as PointerEvent ) . toElement ) ) { chart . pointer . reset ( ) ; chart . pointer . chartPosition = void 0 ; } if ( tooltip && ! tooltip . isHidden ) { this . reset ( ) ; } } private function public onContainerMouseEnter ( e ) { delete this . chartPosition ; } private function public onContainerMouseMove ( e ) { const chart = this . chart ; const pEvt = this . normalize ( e ) ; this . setHoverChartIndex ( ) ; if ( ! pEvt . preventDefault ) { pEvt . returnValue = false ; } if ( chart . mouseIsDown === 'mousedown' || this . touchSelect ( pEvt ) ) { this . drag ( pEvt ) ; } if ( ! chart . openMenu && ( this . inClass ( pEvt . target as any , 'highcharts-tracker' ) || chart . isInsidePlot ( pEvt . chartX - chart . plotLeft , pEvt . chartY - chart . plotTop , { visiblePlotOnly : true } ) ) ) { if ( this . inClass ( pEvt . target as any , 'highcharts-no-tooltip' ) ) { this . reset ( false , 0 ) ; } else { this . runPointActions ( pEvt ) ; } } } private function public onDocumentTouchEnd ( e ) { const hoverChart = charts [ pick ( Pointer . hoverChartIndex , - 1 ) ] ; if ( hoverChart ) { hoverChart . pointer . drop ( e ) ; } } private function public onContainerTouchMove ( e ) { if ( this . touchSelect ( e ) ) { this . onContainerMouseMove ( e ) ; } else { this . touch ( e ) ; } } private function public onContainerTouchStart ( e ) { if ( this . touchSelect ( e ) ) { this . onContainerMouseDown ( e ) ; } else { this . zoomOption ( e ) ; this . touch ( e , true ) ; } } private function public onDocumentMouseMove ( e ) { const chart = this . chart ; const chartPosition = this . chartPosition ; const pEvt = this . normalize ( e , chartPosition ) ; const tooltip = chart . tooltip ; if ( chartPosition && ( ! tooltip || ! tooltip . isStickyOnContact ( ) ) && ! chart . isInsidePlot ( pEvt . chartX - chart . plotLeft , pEvt . chartY - chart . plotTop , { visiblePlotOnly : true } ) && ! this . inClass ( pEvt . target as any , 'highcharts-tracker' ) ) { this . reset ( ) ; } } private function public onDocumentMouseUp ( e ) { const chart = charts [ pick ( Pointer . hoverChartIndex , - 1 ) ] ; if ( chart ) { chart . pointer . drop ( e ) ; } } private function public pinch ( e ) { const self = this , chart = self . chart , pinchDown = self . pinchDown , touches = ( e . touches || [ ] ) , touchesLength = touches . length , lastValidTouch = self . lastValidTouch as any , hasZoom = self . hasZoom , transform = { } as any , fireClickEvent = touchesLength === 1 && ( ( self . inClass ( e . target as any , 'highcharts-tracker' ) && chart . runTrackerClick ) || self . runChartClick ) , clip = { } ; let selectionMarker = self . selectionMarker ; if ( touchesLength > 1 ) { self . initiated = true ; } else if ( touchesLength === 1 && this . followTouchMove ) { self . initiated = false ; } if ( hasZoom && self . initiated && ! fireClickEvent && e . cancelable !== false ) { e . preventDefault ( ) ; } [ ] . map . call ( touches , function ( e ) : PointerEvent { return self . normalize ( e ) ; } ) ; if ( e . type === 'touchstart' ) { [ ] . forEach . call ( touches , function ( e , i ) : void { 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 ] ; chart . axes . forEach ( function ( axis ) : void { if ( axis . zoomEnabled ) { const bounds = chart . bounds [ axis . horiz ? 'h' : 'v' ] , minPixelPadding = axis . minPixelPadding , min = axis . toPixels ( Math . min ( pick ( axis . options . min , axis . dataMin as any ) , axis . dataMin as any ) ) , max = axis . toPixels ( Math . max ( pick ( axis . options . max , axis . dataMax as any ) , axis . dataMax as any ) ) , absMin = Math . min ( min , max ) , absMax = Math . max ( min , max ) ; bounds . min = Math . min ( axis . pos , absMin - minPixelPadding ) ; bounds . max = Math . max ( axis . pos + axis . len , absMax + minPixelPadding ) ; } } ) ; self . res = true ; } else if ( self . followTouchMove && touchesLength === 1 ) { this . runPointActions ( self . normalize ( e ) ) ; } else if ( pinchDown . length ) { fireEvent ( chart , 'touchpan' , { originalEvent : e } , ( ) : void => { if ( ! selectionMarker ) { self . selectionMarker = selectionMarker = extend ( { destroy : noop , touch : true } , chart . plotBox as any ) as any ; } self . pinchTranslate ( pinchDown , touches as any , transform , selectionMarker , clip , lastValidTouch ) ; self . hasPinched = hasZoom ; self . scaleGroups ( transform , clip as any ) ; } ) ; if ( self . res ) { self . res = false ; this . reset ( false , 0 ) ; } } } private function public pinchTranslate ( 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 ) ; } } private function public pinchTranslateDirection ( horiz , pinchDown , touches , transform , selectionMarker , clip , lastValidTouch , forcedScale ? ) { const chart = this . chart , xy = horiz ? 'x' : 'y' , XY = horiz ? 'X' : 'Y' , sChartXY = ( 'chart' + XY ) as any , wh = horiz ? 'width' : 'height' , plotLeftTop = ( chart as any ) [ 'plot' + ( horiz ? 'Left' : 'Top' ) ] , inverted = chart . inverted , bounds = chart . bounds [ horiz ? 'h' : 'v' ] , singleTouch = pinchDown . length === 1 , touch0Start = pinchDown [ 0 ] [ sChartXY ] , touch1Start = ! singleTouch && pinchDown [ 1 ] [ sChartXY ] , setScale = function ( ) : void { if ( typeof touch1Now === 'number' && Math . abs ( touch0Start - touch1Start ) > 20 ) { scale = forcedScale || Math . abs ( touch0Now - touch1Now ) / Math . abs ( touch0Start - touch1Start ) ; } clipXY = ( ( plotLeftTop - touch0Now ) / scale ) + touch0Start ; selectionWH = ( chart as any ) [ 'plot' + ( horiz ? 'Width' : 'Height' ) ] / scale ; } ; let selectionWH , selectionXY , clipXY , scale = forcedScale || 1 , touch0Now = touches [ 0 ] [ sChartXY ] , touch1Now = ! singleTouch && touches [ 1 ] [ sChartXY ] , outOfBounds ; setScale ( ) ; selectionXY = clipXY ; if ( selectionXY < bounds . min ) { selectionXY = bounds . min ; outOfBounds = true ; } else if ( selectionXY + selectionWH > bounds . max ) { selectionXY = bounds . max - selectionWH ; outOfBounds = true ; } if ( outOfBounds ) { touch0Now -= 0.8 * ( touch0Now - lastValidTouch [ xy ] [ 0 ] ) ; if ( typeof touch1Now === 'number' ) { touch1Now -= 0.8 * ( touch1Now - lastValidTouch [ xy ] [ 1 ] ) ; } setScale ( ) ; } else { lastValidTouch [ xy ] = [ touch0Now , touch1Now ] ; } if ( ! inverted ) { clip [ xy ] = clipXY - plotLeftTop ; clip [ wh ] = selectionWH ; } const scaleKey = inverted ? ( horiz ? 'scaleY' : 'scaleX' ) : 'scale' + XY ; const transformScale = inverted ? 1 / scale : scale ; selectionMarker [ wh ] = selectionWH ; selectionMarker [ xy ] = selectionXY ; transform [ scaleKey ] = scale ; transform [ 'translate' + XY ] = ( transformScale * plotLeftTop ) + ( touch0Now - ( transformScale * touch0Start ) ) ; } function param { boolean } allowMove param { number } delay public reset ( allowMove ? , delay ? ) { const pointer = this , chart = pointer . chart , hoverSeries = chart . hoverSeries , hoverPoint = chart . hoverPoint , hoverPoints = chart . hoverPoints , tooltip = chart . tooltip , tooltipPoints = tooltip && tooltip . shared ? hoverPoints : hoverPoint ; if ( allowMove && tooltipPoints ) { splat ( tooltipPoints ) . forEach ( function ( point ) : void { if ( point . series . isCartesian && typeof point . plotX === 'undefined' ) { allowMove = false ; } } ) ; } if ( allowMove ) { if ( tooltip && tooltipPoints && splat ( tooltipPoints ) . length ) { tooltip . refresh ( tooltipPoints ) ; if ( tooltip . shared && hoverPoints ) { hoverPoints . forEach ( function ( point ) : void { point . setState ( point . state , true ) ; if ( point . series . isCartesian ) { if ( point . series . xAxis . crosshair ) { point . series . xAxis . drawCrosshair ( null as any , point ) ; } if ( point . series . yAxis . crosshair ) { point . series . yAxis . drawCrosshair ( null as any , point ) ; } } } ) ; } else if ( hoverPoint ) { hoverPoint . setState ( hoverPoint . state , true ) ; chart . axes . forEach ( function ( axis ) : void { if ( axis . crosshair && ( hoverPoint as any ) . series [ axis . coll ] === axis ) { axis . drawCrosshair ( null as any , hoverPoint ) ; } } ) ; } } } else { if ( hoverPoint ) { hoverPoint . onMouseOut ( ) ; } if ( hoverPoints ) { hoverPoints . forEach ( function ( point ) : void { point . setState ( ) ; } ) ; } if ( hoverSeries ) { hoverSeries . onMouseOut ( ) ; } if ( tooltip ) { tooltip . hide ( delay ) ; } if ( pointer . unDocMouseMove ) { pointer . unDocMouseMove = pointer . unDocMouseMove ( ) ; } chart . axes . forEach ( function ( axis ) : void { axis . hideCrosshair ( ) ; } ) ; pointer . hoverX = chart . hoverPoints = chart . hoverPoint = null as any ; } } private function emits emits public runPointActions ( e , p ? ) { const pointer = this , chart = pointer . chart , series = chart . series , tooltip = ( chart . tooltip && chart . tooltip . options . enabled ? chart . tooltip : void 0 ) , shared = ( tooltip ? tooltip . shared : false ) ; let hoverPoint = p || chart . hoverPoint , hoverSeries = hoverPoint && hoverPoint . series || chart . hoverSeries ; const isDirectTouch = ( ! e || e . type !== 'touchmove' ) && ( ! ! p || ( ( hoverSeries && hoverSeries . directTouch ) && pointer . isDirectTouch ) ) , hoverData = this . getHoverData ( hoverPoint , hoverSeries , series , isDirectTouch , shared , e ) ; hoverPoint = hoverData . hoverPoint ; hoverSeries = hoverData . hoverSeries ; const points = hoverData . hoverPoints , followPointer = hoverSeries && hoverSeries . tooltipOptions . followPointer && ! hoverSeries . tooltipOptions . split , useSharedTooltip = ( shared && hoverSeries && ! hoverSeries . noSharedTooltip ) ; if ( hoverPoint && ( hoverPoint !== chart . hoverPoint || ( tooltip && tooltip . isHidden ) ) ) { ( chart . hoverPoints || [ ] ) . forEach ( function ( p ) : void { if ( points . indexOf ( p ) === - 1 ) { p . setState ( ) ; } } ) ; if ( chart . hoverSeries !== hoverSeries ) { hoverSeries . onMouseOver ( ) ; } pointer . applyInactiveState ( points ) ; ( points || [ ] ) . forEach ( function ( p ) : void { p . setState ( 'hover' ) ; } ) ; if ( chart . hoverPoint ) { chart . hoverPoint . firePointEvent ( 'mouseOut' ) ; } if ( ! hoverPoint . series ) { return ; } name type { Array < Highcharts . Point > | null } chart . hoverPoints = points ; name type { Highcharts . Point | null } chart . hoverPoint = hoverPoint ; /**
             * Hover state should not be lost when axis is updated (#12569)
             * Axis.update runs pointer.reset which uses chart.hoverPoint.state
             * to apply state which does not exist in hoverPoint yet.
             * The mouseOver event should be triggered when hoverPoint
             * is correct.
             */ hoverPoint . firePointEvent ( 'mouseOver' , void 0 , ( ) : void => { if ( tooltip && hoverPoint ) { tooltip . refresh ( useSharedTooltip ? points : hoverPoint , e ) ; } } ) ; } else if ( followPointer && tooltip && ! tooltip . isHidden ) { const anchor = tooltip . getAnchor ( [ { } as any ] , e ) ; if ( chart . isInsidePlot ( anchor [ 0 ] , anchor [ 1 ] , { visiblePlotOnly : true } ) ) { tooltip . updatePosition ( { plotX : anchor [ 0 ] , plotY : anchor [ 1 ] } as any ) ; } } if ( ! pointer . unDocMouseMove ) { pointer . unDocMouseMove = addEvent ( chart . container . ownerDocument , 'mousemove' , function ( e ) : void { const chart = charts [ Pointer . hoverChartIndex as any ] ; if ( chart ) { chart . pointer . onDocumentMouseMove ( e ) ; } } ) ; pointer . eventsToUnbind . push ( pointer . unDocMouseMove ) ; } chart . axes . forEach ( function drawAxisCrosshair ( axis ) : void { const snap = pick ( ( axis . crosshair || { } ) . snap , true ) ; let point ; if ( snap ) { point = chart . hoverPoint ; if ( ! point || ( point . series as any ) [ axis . coll ] !== axis ) { point = find ( points , ( p ) : boolean => p . series && ( p . series as any ) [ axis . coll ] === axis ) ; } } if ( point || ! snap ) { axis . drawCrosshair ( e , point ) ; } else { axis . hideCrosshair ( ) ; } } ) ; } private function public scaleGroups ( attribs ? , clip ? ) { const chart = this . chart ; chart . series . forEach ( function ( series ) : void { const seriesAttribs = attribs || series . getPlotBox ( ) ; if ( series . group && ( ( series . xAxis && series . xAxis . zoomEnabled ) || chart . mapView ) ) { series . group . attr ( seriesAttribs ) ; if ( series . markerGroup ) { series . markerGroup . attr ( seriesAttribs ) ; series . markerGroup . clip ( clip ? ( chart . clipRect as any ) : ( null as any ) ) ; } if ( series . dataLabelsGroup ) { series . dataLabelsGroup . attr ( seriesAttribs ) ; } } } ) ; ( chart . clipRect as any ) . attr ( clip || chart . clipBox ) ; } private function public setDOMEvents ( ) { const container = this . chart . container , ownerDoc = container . ownerDocument ; container . onmousedown = this . onContainerMouseDown . bind ( this ) ; container . onmousemove = this . onContainerMouseMove . bind ( this ) ; container . onclick = this . onContainerClick . bind ( this ) ; this . eventsToUnbind . push ( addEvent ( container , 'mouseenter' , this . onContainerMouseEnter . bind ( this ) ) ) ; this . eventsToUnbind . push ( addEvent ( container , 'mouseleave' , this . onContainerMouseLeave . bind ( this ) ) ) ; if ( ! Pointer . unbindDocumentMouseUp ) { Pointer . unbindDocumentMouseUp = addEvent ( ownerDoc , 'mouseup' , this . onDocumentMouseUp . bind ( this ) ) ; } let parent = this . chart . renderTo . parentElement ; while ( parent && parent . tagName !== 'BODY' ) { this . eventsToUnbind . push ( addEvent ( parent , 'scroll' , ( ) : void => { delete this . chartPosition ; } ) ) ; parent = parent . parentElement ; } if ( H . hasTouch ) { this . eventsToUnbind . push ( addEvent ( container , 'touchstart' , this . onContainerTouchStart . bind ( this ) , { passive : false } ) ) ; this . eventsToUnbind . push ( addEvent ( container , 'touchmove' , this . onContainerTouchMove . bind ( this ) , { passive : false } ) ) ; if ( ! Pointer . unbindDocumentTouchEnd ) { Pointer . unbindDocumentTouchEnd = addEvent ( ownerDoc , 'touchend' , this . onDocumentTouchEnd . bind ( this ) , { passive : false } ) ; } } } private function public setHoverChartIndex ( ) { const chart = this . chart ; const hoverChart = H . charts [ pick ( Pointer . hoverChartIndex , - 1 ) ] ; if ( hoverChart && hoverChart !== chart ) { hoverChart . pointer . onContainerMouseLeave ( { relatedTarget : true } as any ) ; } if ( ! hoverChart || ! hoverChart . mouseIsDown ) { Pointer . hoverChartIndex = chart . index ; } } private function public touch ( e , start ? ) { const chart = this . chart ; let hasMoved , pinchDown , isInside ; this . setHoverChartIndex ( ) ; if ( ( e as any ) . touches . length === 1 ) { e = this . normalize ( e ) ; isInside = chart . isInsidePlot ( e . chartX - chart . plotLeft , e . chartY - chart . plotTop , { visiblePlotOnly : true } ) ; if ( isInside && ! chart . openMenu ) { if ( start ) { this . runPointActions ( e ) ; } if ( e . type === 'touchmove' ) { pinchDown = this . pinchDown ; hasMoved = pinchDown [ 0 ] ? Math . sqrt ( 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 ) { this . reset ( ) ; } } else if ( ( e as any ) . touches . length === 2 ) { this . pinch ( e ) ; } } private function private touchSelect ( e ) { return Boolean ( this . chart . options . chart . zoomBySingleTouch && e . touches && e . touches . length === 1 ) ; } private function public zoomOption ( e ) { const chart = this . chart , options = chart . options . chart , inverted = chart . inverted ; let zoomType = options . zoomType || '' , zoomX , zoomY ; if ( /touch/ . test ( e . type ) ) { zoomType = pick ( options . pinchType , zoomType ) ; } this . zoomX = zoomX = /x/ . test ( zoomType ) ; this . zoomY = zoomY = /y/ . test ( zoomType ) ; this . zoomHor = ( zoomX && ! inverted ) || ( zoomY && inverted ) ; this . zoomVert = ( zoomY && ! inverted ) || ( zoomX && inverted ) ; this . hasZoom = zoomX || zoomY ; } } namespace Pointer { export interface ChartPositionObject { left : number ; scaleX : number ; scaleY : number ; top : number ; } export interface AxesCoordinatesObject { xAxis : Array < AxisCoordinateObject > ; yAxis : Array < AxisCoordinateObject > ; } export interface AxisCoordinateObject { axis : Axis ; value : number ; } export interface CoordinatesObject { chartX : number ; chartY : number ; } export interface EventArgsObject { chartX ? : number ; chartY ? : number ; filter ? : Function ; hoverPoint ? : Point ; shared ? : boolean ; } export interface HoverDataObject { hoverPoint ? : Point ; hoverPoints : Array < Point > ; hoverSeries : Series ; } export interface SelectDataObject { axis : Axis ; max : number ; min : number ; } export interface SelectEventObject { originalEvent : Event ; xAxis : Array < SelectDataObject > ; yAxis : Array < SelectDataObject > ; } } export default Pointer ; interface name type { Highcharts . Axis } name type { number } interface name type { Array < Highcharts . PointerAxisCoordinateObject > } name type { Array < Highcharts . PointerAxisCoordinateObject > } interface name type { number } name type { number } /**
 * A native browser mouse or touch event, extended with position information
 * relative to the  Chart . container .
 *
 *  interface extends global . PointerEvent name type { number } name type { number } interface name type { Highcharts . Axis } name type { number } name type { number } interface name type { global . Event } name type { Array < Highcharts . SelectDataObject > } name type { Array < Highcharts . SelectDataObject > } interface name type { number } name type { number } name type { number } name type { number } '' ;