/* * 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 = { 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) .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; }