blob: 1b6aad2e188ab80a9ad175a3691a516cbff13c2a [file] [log] [blame]
/*
* 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;
}