Spaces:
Running
Running
| /* | |
| * 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; | |
| } | |