| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 | // Basic transitions in the same series when shapes are the same. | 
 |  | 
 | import { | 
 |     AnimationOptionMixin, | 
 |     AnimationDelayCallbackParam, | 
 |     PayloadAnimationPart, | 
 |     AnimationOption | 
 | } from '../util/types'; | 
 | import { AnimationEasing } from 'zrender/src/animation/easing'; | 
 | import Element, { ElementAnimateConfig, ElementProps } from 'zrender/src/Element'; | 
 | import Model from '../model/Model'; | 
 | import { | 
 |     isFunction, | 
 |     isObject, | 
 |     retrieve2 | 
 | } from 'zrender/src/core/util'; | 
 | import Displayable from 'zrender/src/graphic/Displayable'; | 
 | import Group from 'zrender/src/graphic/Group'; | 
 | import { makeInner } from '../util/model'; | 
 |  | 
 | // Stored properties for further transition. | 
 |  | 
 | export const transitionStore = makeInner<{ | 
 |     oldStyle: Displayable['style'] | 
 | }, Displayable>(); | 
 |  | 
 |  | 
 | type AnimateOrSetPropsOption = { | 
 |     dataIndex?: number; | 
 |     cb?: () => void; | 
 |     during?: (percent: number) => void; | 
 |     removeOpt?: AnimationOption | 
 |     isFrom?: boolean; | 
 | }; | 
 |  | 
 | /** | 
 |  * Return null if animation is disabled. | 
 |  */ | 
 | export function getAnimationConfig( | 
 |     animationType: 'enter' | 'update' | 'leave', | 
 |     animatableModel: Model<AnimationOptionMixin>, | 
 |     dataIndex: number, | 
 |     // Extra opts can override the option in animatable model. | 
 |     extraOpts?: Pick<ElementAnimateConfig, 'easing' | 'duration' | 'delay'>, | 
 |     // TODO It's only for pictorial bar now. | 
 |     extraDelayParams?: unknown | 
 | ): Pick<ElementAnimateConfig, 'easing' | 'duration' | 'delay'> | null { | 
 |     let animationPayload: PayloadAnimationPart; | 
 |     // Check if there is global animation configuration from dataZoom/resize can override the config in option. | 
 |     // If animation is enabled. Will use this animation config in payload. | 
 |     // If animation is disabled. Just ignore it. | 
 |     if (animatableModel && animatableModel.ecModel) { | 
 |         const updatePayload = animatableModel.ecModel.getUpdatePayload(); | 
 |         animationPayload = (updatePayload && updatePayload.animation) as PayloadAnimationPart; | 
 |     } | 
 |     const animationEnabled = animatableModel && animatableModel.isAnimationEnabled(); | 
 |  | 
 |     const isUpdate = animationType === 'update'; | 
 |  | 
 |     if (animationEnabled) { | 
 |         let duration: number | Function; | 
 |         let easing: AnimationEasing; | 
 |         let delay: number | Function; | 
 |         if (extraOpts) { | 
 |             duration = retrieve2(extraOpts.duration, 200); | 
 |             easing = retrieve2(extraOpts.easing, 'cubicOut'); | 
 |             delay = 0; | 
 |         } | 
 |         else { | 
 |             duration = animatableModel.getShallow( | 
 |                 isUpdate ? 'animationDurationUpdate' : 'animationDuration' | 
 |             ); | 
 |             easing = animatableModel.getShallow( | 
 |                 isUpdate ? 'animationEasingUpdate' : 'animationEasing' | 
 |             ); | 
 |             delay = animatableModel.getShallow( | 
 |                 isUpdate ? 'animationDelayUpdate' : 'animationDelay' | 
 |             ); | 
 |         } | 
 |         // animation from payload has highest priority. | 
 |         if (animationPayload) { | 
 |             animationPayload.duration != null && (duration = animationPayload.duration); | 
 |             animationPayload.easing != null && (easing = animationPayload.easing); | 
 |             animationPayload.delay != null && (delay = animationPayload.delay); | 
 |         } | 
 |         if (isFunction(delay)) { | 
 |             delay = delay( | 
 |                 dataIndex as number, | 
 |                 extraDelayParams | 
 |             ); | 
 |         } | 
 |         if (isFunction(duration)) { | 
 |             duration = duration(dataIndex as number); | 
 |         } | 
 |         const config = { | 
 |             duration: duration as number || 0, | 
 |             delay: delay as number, | 
 |             easing | 
 |         }; | 
 |  | 
 |         return config; | 
 |     } | 
 |     else { | 
 |         return null; | 
 |     } | 
 | } | 
 |  | 
 | function animateOrSetProps<Props>( | 
 |     animationType: 'enter' | 'update' | 'leave', | 
 |     el: Element<Props>, | 
 |     props: Props, | 
 |     animatableModel?: Model<AnimationOptionMixin> & { | 
 |         getAnimationDelayParams?: (el: Element<Props>, dataIndex: number) => AnimationDelayCallbackParam | 
 |     }, | 
 |     dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, | 
 |     cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], | 
 |     during?: AnimateOrSetPropsOption['during'] | 
 | ) { | 
 |     let isFrom = false; | 
 |     let removeOpt: AnimationOption; | 
 |     if (isFunction(dataIndex)) { | 
 |         during = cb; | 
 |         cb = dataIndex; | 
 |         dataIndex = null; | 
 |     } | 
 |     else if (isObject(dataIndex)) { | 
 |         cb = dataIndex.cb; | 
 |         during = dataIndex.during; | 
 |         isFrom = dataIndex.isFrom; | 
 |         removeOpt = dataIndex.removeOpt; | 
 |         dataIndex = dataIndex.dataIndex; | 
 |     } | 
 |  | 
 |     const isRemove = (animationType === 'leave'); | 
 |  | 
 |     if (!isRemove) { | 
 |         // Must stop the remove animation. | 
 |         el.stopAnimation('leave'); | 
 |     } | 
 |  | 
 |     const animationConfig = getAnimationConfig( | 
 |         animationType, | 
 |         animatableModel, | 
 |         dataIndex as number, | 
 |         isRemove ? (removeOpt || {}) : null, | 
 |         (animatableModel && animatableModel.getAnimationDelayParams) | 
 |             ? animatableModel.getAnimationDelayParams(el, dataIndex as number) | 
 |             : null | 
 |     ); | 
 |     if (animationConfig && animationConfig.duration > 0) { | 
 |         const duration = animationConfig.duration; | 
 |         const animationDelay = animationConfig.delay; | 
 |         const animationEasing = animationConfig.easing; | 
 |  | 
 |         const animateConfig: ElementAnimateConfig = { | 
 |             duration: duration as number, | 
 |             delay: animationDelay as number || 0, | 
 |             easing: animationEasing, | 
 |             done: cb, | 
 |             force: !!cb || !!during, | 
 |             // Set to final state in update/init animation. | 
 |             // So the post processing based on the path shape can be done correctly. | 
 |             setToFinal: !isRemove, | 
 |             scope: animationType, | 
 |             during: during | 
 |         }; | 
 |  | 
 |         isFrom | 
 |             ? el.animateFrom(props, animateConfig) | 
 |             : el.animateTo(props, animateConfig); | 
 |     } | 
 |     else { | 
 |         el.stopAnimation(); | 
 |         // If `isFrom`, the props is the "from" props. | 
 |         !isFrom && el.attr(props); | 
 |         // Call during at least once. | 
 |         during && during(1); | 
 |         cb && (cb as AnimateOrSetPropsOption['cb'])(); | 
 |     } | 
 | } | 
 |  | 
 |  | 
 |  | 
 | /** | 
 |  * Update graphic element properties with or without animation according to the | 
 |  * configuration in series. | 
 |  * | 
 |  * Caution: this method will stop previous animation. | 
 |  * So do not use this method to one element twice before | 
 |  * animation starts, unless you know what you are doing. | 
 |  * @example | 
 |  *     graphic.updateProps(el, { | 
 |  *         position: [100, 100] | 
 |  *     }, seriesModel, dataIndex, function () { console.log('Animation done!'); }); | 
 |  *     // Or | 
 |  *     graphic.updateProps(el, { | 
 |  *         position: [100, 100] | 
 |  *     }, seriesModel, function () { console.log('Animation done!'); }); | 
 |  */ | 
 |  function updateProps<Props extends ElementProps>( | 
 |     el: Element<Props>, | 
 |     props: Props, | 
 |     // TODO: TYPE AnimatableModel | 
 |     animatableModel?: Model<AnimationOptionMixin>, | 
 |     dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, | 
 |     cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], | 
 |     during?: AnimateOrSetPropsOption['during'] | 
 | ) { | 
 |     animateOrSetProps('update', el, props, animatableModel, dataIndex, cb, during); | 
 | } | 
 |  | 
 | export {updateProps}; | 
 |  | 
 | /** | 
 |  * Init graphic element properties with or without animation according to the | 
 |  * configuration in series. | 
 |  * | 
 |  * Caution: this method will stop previous animation. | 
 |  * So do not use this method to one element twice before | 
 |  * animation starts, unless you know what you are doing. | 
 |  */ | 
 | export function initProps<Props extends ElementProps>( | 
 |     el: Element<Props>, | 
 |     props: Props, | 
 |     animatableModel?: Model<AnimationOptionMixin>, | 
 |     dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, | 
 |     cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], | 
 |     during?: AnimateOrSetPropsOption['during'] | 
 | ) { | 
 |     animateOrSetProps('enter', el, props, animatableModel, dataIndex, cb, during); | 
 | } | 
 |  | 
 | /** | 
 |  * If element is removed. | 
 |  * It can determine if element is having remove animation. | 
 |  */ | 
 |  export function isElementRemoved(el: Element) { | 
 |     if (!el.__zr) { | 
 |         return true; | 
 |     } | 
 |     for (let i = 0; i < el.animators.length; i++) { | 
 |         const animator = el.animators[i]; | 
 |         if (animator.scope === 'leave') { | 
 |             return true; | 
 |         } | 
 |     } | 
 |     return false; | 
 | } | 
 |  | 
 | /** | 
 |  * Remove graphic element | 
 |  */ | 
 | export function removeElement<Props>( | 
 |     el: Element<Props>, | 
 |     props: Props, | 
 |     animatableModel?: Model<AnimationOptionMixin>, | 
 |     dataIndex?: AnimateOrSetPropsOption['dataIndex'] | AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption, | 
 |     cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], | 
 |     during?: AnimateOrSetPropsOption['during'] | 
 | ) { | 
 |     // Don't do remove animation twice. | 
 |     if (isElementRemoved(el)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     animateOrSetProps('leave', el, props, animatableModel, dataIndex, cb, during); | 
 | } | 
 |  | 
 | function fadeOutDisplayable( | 
 |     el: Displayable, | 
 |     animatableModel?: Model<AnimationOptionMixin>, | 
 |     dataIndex?: number, | 
 |     done?: AnimateOrSetPropsOption['cb'] | 
 | ) { | 
 |     el.removeTextContent(); | 
 |     el.removeTextGuideLine(); | 
 |     removeElement(el, { | 
 |         style: { | 
 |             opacity: 0 | 
 |         } | 
 |     }, animatableModel, dataIndex, done); | 
 | } | 
 |  | 
 | export function removeElementWithFadeOut( | 
 |     el: Element, | 
 |     animatableModel?: Model<AnimationOptionMixin>, | 
 |     dataIndex?: number | 
 | ) { | 
 |     function doRemove() { | 
 |         el.parent && el.parent.remove(el); | 
 |     } | 
 |     // Hide label and labelLine first | 
 |     // TODO Also use fade out animation? | 
 |     if (!el.isGroup) { | 
 |         fadeOutDisplayable(el as Displayable, animatableModel, dataIndex, doRemove); | 
 |     } | 
 |     else { | 
 |         (el as Group).traverse(function (disp: Displayable) { | 
 |             if (!disp.isGroup) { | 
 |                 // Can invoke doRemove multiple times. | 
 |                 fadeOutDisplayable(disp as Displayable, animatableModel, dataIndex, doRemove); | 
 |             } | 
 |         }); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * Save old style for style transition in universalTransition module. | 
 |  * It's used when element will be reused in each render. | 
 |  * For chart like map, heatmap, which will always create new element. | 
 |  * We don't need to save this because universalTransition can get old style from the old element | 
 |  */ | 
 | export function saveOldStyle(el: Displayable) { | 
 |     transitionStore(el).oldStyle = el.style; | 
 | } | 
 |  | 
 | export function getOldStyle(el: Displayable) { | 
 |     return transitionStore(el).oldStyle; | 
 | } |