|  | /* | 
|  | * 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 './basicTransition'; | 
|  | 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 && 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; | 
|  | } |