'use strict' ; import type AnimationOptions from './AnimationOptions' ; import type FxLike from './FxLike' ; import type { HTMLDOMElement } from '../Renderer/DOMElementType' ; import type HTMLElement from '../Renderer/HTML/HTMLElement' ; import type SVGElement from '../Renderer/SVG/SVGElement' ; import type SVGPath from '../Renderer/SVG/SVGPath' ; import Color from '../Color/Color.js' ; const { parse : color } = Color ; import H from '../Globals.js' ; const { win } = H ; import U from '../Utilities.js' ; const { isNumber , objectEach } = U ; /**
 * An animator object used internally. One instance applies to one property
 * (attribute or style prop) on one element. Animation is always initiated
 * through  SVGElement .
 *
 *  example private class name param { Highcharts . HTMLDOMElement | Highcharts . SVGElement } elem param { Partial < Highcharts . AnimationOptionsObject > } options param { string } prop class Fx { public static timers : Array < Fx . Timer > = [ ] ; public constructor ( elem , options , prop ) { this . options = options ; this . elem = elem ; this . prop = prop ; } public elem : ( HTMLElement | SVGElement ) ; public end ? : number ; public from ? : number ; public now ? : number ; public options : Partial < AnimationOptions > ; public paths ? : [ SVGPath , SVGPath ] ; public pos : number = NaN ; public prop : string ; public start ? : number ; public startTime ? : number ; public toD ? : SVGPath ; public unit ? : string ; function public dSetter ( ) { const paths = this . paths , start = paths && paths [ 0 ] , end = paths && paths [ 1 ] , now = this . now || 0 ; let path = [ ] ; if ( now === 1 || ! start || ! end ) { path = this . toD || [ ] ; } else if ( start . length === end . length && now < 1 ) { for ( let i = 0 ; i < end . length ; i ++ ) { const startSeg = start [ i ] ; const endSeg = end [ i ] ; const tweenSeg = [ ] ; for ( let j = 0 ; j < endSeg . length ; j ++ ) { const startItem = startSeg [ j ] ; const endItem = endSeg [ j ] ; if ( isNumber ( startItem ) && isNumber ( endItem ) && ! ( endSeg [ 0 ] === 'A' && ( j === 4 || j === 5 ) ) ) { tweenSeg [ j ] = startItem + now * ( endItem - startItem ) ; } else { tweenSeg [ j ] = endItem ; } } path . push ( tweenSeg as SVGPath . Segment ) ; } } else { path = end ; } this . elem . attr ( 'd' , path , void 0 , true ) ; } function public update ( ) { const elem = this . elem , prop = this . prop , now = this . now as any , step = this . options . step ; if ( ( this as any ) [ prop + 'Setter' ] ) { ( this as any ) [ prop + 'Setter' ] ( ) ; } else if ( elem . attr ) { if ( elem . element ) { elem . attr ( prop , now , null as any , true ) ; } } else { elem . style [ prop as any ] = now + ( this . unit as any ) ; } if ( step ) { step . call ( elem , now , this ) ; } } function param { number } from param { number } to param { string } unit public run ( from , to , unit ) { const self = this , options = self . options , timer = function ( gotoEnd ? ) : boolean { return timer . stopped ? false : self . step ( gotoEnd ) ; } , requestAnimationFrame = win . requestAnimationFrame || function ( step ) : void { setTimeout ( step , 13 ) ; } , step = function ( ) : void { for ( let i = 0 ; i < Fx . timers . length ; i ++ ) { if ( ! Fx . timers [ i ] ( ) ) { Fx . timers . splice ( i -- , 1 ) ; } } if ( Fx . timers . length ) { requestAnimationFrame ( step ) ; } } ; if ( from === to && ! this . elem [ 'forceAnimate:' + this . prop ] ) { delete ( options . curAnim as any ) [ this . prop ] ; if ( options . complete && Object . keys ( options . curAnim as any ) . length === 0 ) { options . complete . call ( this . elem ) ; } } else { this . startTime = + new Date ( ) ; this . start = from ; this . end = to ; this . unit = unit ; this . now = this . start ; this . pos = 0 ; timer . elem = this . elem ; timer . prop = this . prop ; if ( timer ( ) && Fx . timers . push ( timer ) === 1 ) { requestAnimationFrame ( step ) ; } } } function param { boolean } gotoEnd return { boolean } public step ( gotoEnd ? ) { const t = + new Date ( ) , options = this . options , elem = this . elem , complete = options . complete , duration = options . duration as any , curAnim = options . curAnim as any ; let ret , done ; if ( elem . attr && ! elem . element ) { ret = false ; } else if ( gotoEnd || t >= duration + ( this . startTime as any ) ) { this . now = this . end ; this . pos = 1 ; this . update ( ) ; curAnim [ this . prop ] = true ; done = true ; objectEach ( curAnim , function ( val ) : void { if ( val !== true ) { done = false ; } } ) ; if ( done && complete ) { complete . call ( elem ) ; } ret = false ; } else { this . pos = ( options . easing as Function ) ( ( t - ( this . startTime as any ) ) / duration ) ; this . now = ( this . start as any ) + ( ( ( this . end as any ) - ( this . start as any ) ) * ( this . pos as any ) ) ; this . update ( ) ; ret = true ; } return ret ; } function param { Highcharts . SVGElement } elem param { Highcharts . SVGPathArray | undefined } fromD param { Highcharts . SVGPathArray } toD return { Array < Highcharts . SVGPathArray , Highcharts . SVGPathArray > } public initPath ( elem , fromD , toD ) { const startX = elem . startX , endX = elem . endX , end = toD . slice ( ) , isArea = elem . isArea , positionFactor = isArea ? 2 : 1 ; let shift , fullLength , i , reverse , start = fromD && fromD . slice ( ) ; if ( ! start ) { return [ end , end ] ; } private function prepend ( arr , other ) { while ( arr . length < fullLength ) { const moveSegment = arr [ 0 ] , otherSegment = other [ fullLength - arr . length ] ; if ( otherSegment && moveSegment [ 0 ] === 'M' ) { if ( otherSegment [ 0 ] === 'C' ) { arr [ 0 ] = [ 'C' , moveSegment [ 1 ] , moveSegment [ 2 ] , moveSegment [ 1 ] , moveSegment [ 2 ] , moveSegment [ 1 ] , moveSegment [ 2 ] ] ; } else { arr [ 0 ] = [ 'L' , moveSegment [ 1 ] , moveSegment [ 2 ] ] ; } } arr . unshift ( moveSegment ) ; if ( isArea ) { const z = arr . pop ( ) ; arr . push ( arr [ arr . length - 1 ] , z ) ; } } } private function append ( arr , other ) { while ( arr . length < fullLength ) { const segmentToAdd = arr [ Math . floor ( arr . length / positionFactor ) - 1 ] . slice ( ) ; if ( segmentToAdd [ 0 ] === 'C' ) { segmentToAdd [ 1 ] = segmentToAdd [ 5 ] ; segmentToAdd [ 2 ] = segmentToAdd [ 6 ] ; } if ( ! isArea ) { arr . push ( segmentToAdd as SVGPath . Segment ) ; } else { const lowerSegmentToAdd = arr [ Math . floor ( arr . length / positionFactor ) ] . slice ( ) ; arr . splice ( arr . length / 2 , 0 , segmentToAdd as SVGPath . Segment , lowerSegmentToAdd as SVGPath . Segment ) ; } } } if ( startX && endX && endX . length ) { for ( i = 0 ; i < startX . length ; i ++ ) { if ( startX [ i ] === endX [ 0 ] ) { shift = i ; break ; } else if ( startX [ 0 ] === endX [ endX . length - startX . length + i ] ) { shift = i ; reverse = true ; break ; } else if ( startX [ startX . length - 1 ] === endX [ endX . length - startX . length + i ] ) { shift = startX . length - i ; break ; } } if ( typeof shift === 'undefined' ) { start = [ ] ; } } if ( start . length && isNumber ( shift ) ) { fullLength = end . length + shift * positionFactor ; if ( ! reverse ) { prepend ( end , start ) ; append ( start , end ) ; } else { prepend ( start , end ) ; append ( end , start ) ; } } return [ start , end ] ; } function public fillSetter ( ) { Fx . prototype . strokeSetter . apply ( this , arguments as any ) ; } function public strokeSetter ( ) { this . elem . attr ( this . prop , color ( this . start as any ) . tweenTo ( color ( this . end as any ) , this . pos as any ) , void 0 , true ) ; } } interface Fx extends FxLike { } namespace Fx { export interface Timer { ( gotoEnd ? ) : boolean ; elem ? : ( HTMLDOMElement | SVGElement ) ; prop ? : string ; stopped ? : boolean ; } } export default Fx ;