| /* |
| * 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. |
| */ |
| |
| import { |
| separateMorph, |
| combineMorph, |
| morphPath, |
| DividePath, |
| isCombineMorphing, |
| SeparateConfig |
| } from 'zrender/src/tool/morphPath'; |
| import { Path } from '../util/graphic'; |
| import SeriesModel from '../model/Series'; |
| import Element, { ElementAnimateConfig } from 'zrender/src/Element'; |
| import { defaults, isArray} from 'zrender/src/core/util'; |
| import { getAnimationConfig } from './basicTrasition'; |
| import { ECElement, UniversalTransitionOption } from '../util/types'; |
| import { clonePath } from 'zrender/src/tool/path'; |
| import Model from '../model/Model'; |
| |
| |
| type DescendentElements = Element[]; |
| type DescendentPaths = Path[]; |
| |
| function isMultiple(elements: DescendentElements | DescendentElements[]): elements is DescendentElements[] { |
| return isArray(elements[0]); |
| } |
| |
| interface MorphingBatch { |
| one: Path; |
| many: Path[]; |
| } |
| |
| function prepareMorphBatches(one: DescendentPaths, many: DescendentPaths[]) { |
| const batches: MorphingBatch[] = []; |
| const batchCount = one.length; |
| for (let i = 0; i < batchCount; i++) { |
| batches.push({ |
| one: one[i], |
| many: [] |
| }); |
| } |
| |
| for (let i = 0; i < many.length; i++) { |
| const len = many[i].length; |
| let k; |
| for (k = 0; k < len; k++) { |
| batches[k % batchCount].many.push(many[i][k]); |
| } |
| } |
| |
| let off = 0; |
| // If one has more paths than each one of many. average them. |
| for (let i = batchCount - 1; i >= 0; i--) { |
| if (!batches[i].many.length) { |
| const moveFrom = batches[off].many; |
| if (moveFrom.length <= 1) { // Not enough |
| // Start from the first one. |
| if (off) { |
| off = 0; |
| } |
| else { |
| return batches; |
| } |
| } |
| const len = moveFrom.length; |
| const mid = Math.ceil(len / 2); |
| batches[i].many = moveFrom.slice(mid, len); |
| batches[off].many = moveFrom.slice(0, mid); |
| |
| off++; |
| } |
| } |
| |
| return batches; |
| } |
| |
| const pathDividers: Record<UniversalTransitionOption['divideShape'], DividePath> = { |
| clone(params) { |
| const ret: Path[] = []; |
| // Fitting the alpha |
| const approxOpacity = 1 - Math.pow(1 - params.path.style.opacity, 1 / params.count); |
| for (let i = 0; i < params.count; i++) { |
| const cloned = clonePath(params.path); |
| cloned.setStyle('opacity', approxOpacity); |
| ret.push(cloned); |
| } |
| return ret; |
| }, |
| // Use the default divider |
| split: null |
| }; |
| |
| export function applyMorphAnimation( |
| from: DescendentPaths | DescendentPaths[], |
| to: DescendentPaths | DescendentPaths[], |
| divideShape: UniversalTransitionOption['divideShape'], |
| seriesModel: SeriesModel, |
| dataIndex: number, |
| animateOtherProps: ( |
| fromIndividual: Path, |
| toIndividual: Path, |
| rawFrom: Path, |
| rawTo: Path, |
| animationCfg: ElementAnimateConfig |
| ) => void |
| ) { |
| if (!from.length || !to.length) { |
| return; |
| } |
| |
| const updateAnimationCfg = getAnimationConfig('update', seriesModel, dataIndex); |
| if (!(updateAnimationCfg.duration > 0)) { |
| return; |
| } |
| const animationDelay = (seriesModel.getModel('universalTransition') as Model<UniversalTransitionOption>) |
| .get('delay'); |
| |
| |
| const animationCfg = Object.assign({ |
| // Need to setToFinal so the further calculation based on the style can be correct. |
| // Like emphasis color. |
| setToFinal: true |
| } as SeparateConfig, updateAnimationCfg); |
| |
| |
| let many: DescendentPaths[]; |
| let one: DescendentPaths; |
| if (isMultiple(from)) { // manyToOne |
| many = from; |
| one = to as DescendentPaths; |
| } |
| if (isMultiple(to)) { // oneToMany |
| many = to; |
| one = from as DescendentPaths; |
| } |
| |
| function morphOneBatch( |
| batch: MorphingBatch, |
| fromIsMany: boolean, |
| animateIndex: number, |
| animateCount: number, |
| forceManyOne?: boolean |
| ) { |
| const batchMany = batch.many; |
| const batchOne = batch.one; |
| if (batchMany.length === 1 && !forceManyOne) { |
| // Is one to one |
| const batchFrom: Path = fromIsMany ? batchMany[0] : batchOne; |
| const batchTo: Path = fromIsMany ? batchOne : batchMany[0]; |
| |
| if (isCombineMorphing(batchFrom as Path)) { |
| // Keep doing combine animation. |
| morphOneBatch({ |
| many: [batchFrom as Path], |
| one: batchTo as Path |
| }, true, animateIndex, animateCount, true); |
| } |
| else { |
| const individualAnimationCfg = animationDelay ? defaults({ |
| delay: animationDelay(animateIndex, animateCount) |
| } as ElementAnimateConfig, animationCfg) : animationCfg; |
| morphPath(batchFrom, batchTo, individualAnimationCfg); |
| animateOtherProps(batchFrom, batchTo, batchFrom, batchTo, individualAnimationCfg); |
| } |
| } |
| else { |
| const separateAnimationCfg = defaults({ |
| dividePath: pathDividers[divideShape], |
| individualDelay: animationDelay && function (idx, count, fromPath, toPath) { |
| return animationDelay(idx + animateIndex, animateCount); |
| } |
| } as SeparateConfig, animationCfg); |
| |
| const { |
| fromIndividuals, |
| toIndividuals |
| } = fromIsMany |
| ? combineMorph(batchMany, batchOne, separateAnimationCfg) |
| : separateMorph(batchOne, batchMany, separateAnimationCfg); |
| |
| const count = fromIndividuals.length; |
| for (let k = 0; k < count; k++) { |
| const individualAnimationCfg = animationDelay ? defaults({ |
| delay: animationDelay(k, count) |
| } as ElementAnimateConfig, animationCfg) : animationCfg; |
| animateOtherProps( |
| fromIndividuals[k], |
| toIndividuals[k], |
| fromIsMany ? batchMany[k] : batch.one, |
| fromIsMany ? batch.one : batchMany[k], |
| individualAnimationCfg |
| ); |
| } |
| } |
| } |
| |
| const fromIsMany = many |
| ? many === from |
| // Is one to one. If the path number not match. also needs do merge and separate morphing. |
| : from.length > to.length; |
| |
| const morphBatches = many |
| ? prepareMorphBatches(one, many) |
| : prepareMorphBatches( |
| (fromIsMany ? to : from) as DescendentPaths, |
| [(fromIsMany ? from : to) as DescendentPaths] |
| ); |
| let animateCount = 0; |
| for (let i = 0; i < morphBatches.length; i++) { |
| animateCount += morphBatches[i].many.length; |
| } |
| let animateIndex = 0; |
| for (let i = 0; i < morphBatches.length; i++) { |
| morphOneBatch(morphBatches[i], fromIsMany, animateIndex, animateCount); |
| animateIndex += morphBatches[i].many.length; |
| } |
| } |
| |
| export function getPathList( |
| elements: Element |
| ): DescendentPaths; |
| export function getPathList( |
| elements: Element[] |
| ): DescendentPaths[]; |
| export function getPathList( |
| elements: Element | Element[] |
| ): DescendentPaths | DescendentPaths[] { |
| if (!elements) { |
| return []; |
| } |
| |
| if (isArray(elements)) { |
| const pathList = []; |
| for (let i = 0; i < elements.length; i++) { |
| pathList.push(getPathList(elements[i])); |
| } |
| return pathList as DescendentPaths[]; |
| } |
| |
| const pathList: DescendentPaths = []; |
| |
| elements.traverse(el => { |
| if ((el instanceof Path) && !(el as ECElement).disableMorphing && !el.invisible && !el.ignore) { |
| pathList.push(el); |
| } |
| }); |
| return pathList; |
| } |