Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

为 Geometry 添加动画 #1553

Merged
merged 30 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5baf588
chore: remove theme.ts for test
simaQ Oct 16, 2019
8070789
feat: add view.animate()
simaQ Oct 17, 2019
f243950
feat: add geometry.animate()
simaQ Oct 17, 2019
9948207
refactor: redefine Shape relative types.
simaQ Oct 17, 2019
7740b00
fix: modify getDrawCfg() return type
simaQ Oct 17, 2019
4e2d7f3
feat: add animate module.
simaQ Oct 17, 2019
0854271
feat: implement destroy method for shape.
simaQ Oct 17, 2019
e6a5063
feat: add animate property for shape
simaQ Oct 17, 2019
69c4605
feat: add animation for interval shape
simaQ Oct 17, 2019
3b16b97
test: add test case for splitPoints() method.
simaQ Oct 17, 2019
70a909e
fix: fix error variables.
simaQ Oct 17, 2019
69f0d18
refactor: remove ShapeBase's getCoordinate() method
simaQ Oct 18, 2019
9a8f268
test: add more test case for element.getAnimateCfg()
simaQ Oct 18, 2019
8e7d740
chore: add comment
simaQ Oct 18, 2019
0838e61
feat: if model is empty, do not call drawShape()
simaQ Oct 18, 2019
b2faafb
feat: add Interval shapes
simaQ Oct 18, 2019
c40270c
test: add test for Interval shapes.
simaQ Oct 18, 2019
af4e739
fix: fix element.getAnimateCfg() bug.
simaQ Oct 21, 2019
5119ef2
feat: pass coordinate to doAnimate()
simaQ Oct 21, 2019
f3a0823
feat: add animation for line shapes
simaQ Oct 21, 2019
afb127d
feat: add some default animations
simaQ Oct 21, 2019
2567cc7
feat: pass more parameters to doAnimate()
simaQ Oct 21, 2019
f488565
feat: add tool functions for animation
simaQ Oct 21, 2019
81b931f
fix: strong element.setState()
simaQ Oct 21, 2019
c35dbd5
fix: fix the error path
simaQ Oct 21, 2019
9a379db
chore: add type define for points
simaQ Oct 21, 2019
8ffa4b2
chore: remove unused import
simaQ Oct 21, 2019
c729c1a
chore: add type define for animateCfg
simaQ Oct 21, 2019
2eb7e4a
refactor: store animate status in view.options
simaQ Oct 21, 2019
5e8242c
feat: store origin in shape
simaQ Oct 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/chart/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export default class View extends EE {
// 分面类实例
protected facetInstance: Facet;

private doAnimation: boolean = true;

constructor(props: ViewCfg) {
super();

Expand Down Expand Up @@ -372,7 +374,13 @@ export default class View extends EE {
return this;
}

public animate(): View {
/*
* 开启或者关闭动画
* @param status 动画状态,true 表示开始,false 表示关闭
* @returns
*/
public animate(status: boolean): View {
this.doAnimation = status;
hustcc marked this conversation as resolved.
Show resolved Hide resolved
return this;
}

Expand Down Expand Up @@ -857,8 +865,13 @@ export default class View extends EE {
* @private
*/
private paintGeometries() {
const doAnimation = this.doAnimation;
// geometry 的 paint 阶段
this.geometries.map((geometry: Geometry) => {
hustcc marked this conversation as resolved.
Show resolved Hide resolved
if (!doAnimation) {
// 如果 view 不执行动画,那么 view 下所有的 geometry 都不执行动画
geometry.animate(false);
}
geometry.paint();
});
}
Expand Down
160 changes: 160 additions & 0 deletions src/geometry/animate/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { transform } from '@antv/matrix-util';
import { ShapeDrawCFG } from 'interface';
import { Coordinate, IGroup, IShape } from '../../dependents';
import { getAngle, getCoordinateClipCfg, getSectorPath } from './util';

/**
* 垂直方向的缩放动画
* @param shape
* @param animateCfg
* @param shapeModel
*/
export function scaleInY(shape: IShape | IGroup, animateCfg, coordinate: Coordinate, shapeModel: ShapeDrawCFG) {
simaQ marked this conversation as resolved.
Show resolved Hide resolved
const box = shape.getBBox();
const x = (box.minX + box.maxX) / 2;
const { points } = shapeModel;
// 数值如果为负值,那么应该从上往下生长,通过 shape 的关键点进行判断
const y = points[0].y - points[1].y <= 0 ? box.maxY : box.minY;

const v = [x, y, 1];
shape.applyToMatrix(v);

const matrix = transform(shape.getMatrix(), [['t', -x, -y], ['s', 1, 0.01], ['t', x, y]]);
shape.setMatrix(matrix);

const endState = {
matrix: transform(shape.getMatrix(), [['t', -x, -y], ['s', 1, 100], ['t', x, y]]),
};

shape.animate(endState, animateCfg);
}

/**
* x 方向的缩放动画
* @param shape
* @param animateCfg
* @param shapeModel
*/
export function scaleInX(shape: IShape | IGroup, animateCfg, coordinate: Coordinate, shapeModel: ShapeDrawCFG) {
const box = shape.getBBox();
const { points } = shapeModel;
// x 数值如果为负值,那么应该从右往左生长
const x = points[0].y - points[1].y > 0 ? box.maxX : box.minX;
const y = (box.minY + box.maxY) / 2;

const v = [x, y, 1];
shape.applyToMatrix(v);

const matrix = transform(shape.getMatrix(), [['t', -x, -y], ['s', 0.01, 1], ['t', x, y]]);
shape.setMatrix(matrix);

const endState = {
matrix: transform(shape.getMatrix(), [['t', -x, -y], ['s', 100, 1], ['t', x, y]]),
};

shape.animate(endState, animateCfg);
}

/**
* 渐隐动画
* @param shape
* @param animateCfg
* @param shapeModel
*/
export function fadeOut(shape: IShape | IGroup, animateCfg, coordinate: Coordinate, shapeModel: ShapeDrawCFG) {
const endState = {
fillOpacity: 0,
strokeOpacity: 0,
};
const { easing, duration, delay } = animateCfg;
shape.animate(
endState,
duration,
easing,
() => {
shape.remove(true);
},
delay
);
}

// TODO: 待 G 4.0 修复,关联 issue https://github.com/antvis/g/issues/218
export function clipIn(shape: IShape | IGroup, animateCfg, coordinate: Coordinate, shapeModel: ShapeDrawCFG) {
const clipCfg = getCoordinateClipCfg(coordinate); // 根据坐标系类型获取整体的剪切区域配置信息
const endState = clipCfg.endState;
// 为 shape 设置剪切区域
const clipShape = shape.setClip(clipCfg);

// 对剪切图形做动画
const { easing, duration, delay } = animateCfg;
clipShape.animate(
endState,
duration,
easing,
() => {
if (shape && !shape.get('destroyed')) {
shape.set('clipShape', null);
}
},
delay
);
}

// TODO: 待 G 4.0 修复,关联 issue https://github.com/antvis/g/issues/218
export function pieChartEnter(shape: IShape | IGroup, animateCfg, coordinate: Coordinate, shapeModel: ShapeDrawCFG) {
const { endAngle } = getAngle(shapeModel, coordinate);
const coordinateStartAngle = coordinate.startAngle;
const center = coordinate.getCenter();
// @ts-ignore FIXME: coordinate 支持下
const radius = coordinate.getRadius();

const startPath = getSectorPath(center.x, center.y, radius, coordinateStartAngle, coordinateStartAngle);
const clipShape = shape.setClip({
type: 'path',
attrs: {
path: startPath,
},
});
const { easing, duration, delay } = animateCfg;
clipShape.animate(
(ratio) => {
const diff = (endAngle - coordinateStartAngle) * ratio + coordinateStartAngle;
const path = getSectorPath(center.x, center.y, radius, coordinateStartAngle, diff);
return {
path,
};
},
duration,
easing,
() => {
if (shape && !shape.get('destroyed')) {
shape.set('clipShape', null);
}
},
delay
);
}

export function pieChartUpdate(
shape: IShape | IGroup,
animateCfg,
coordinate: Coordinate,
shapeModel: ShapeDrawCFG,
toAttrs
) {
const { startAngle: currentStartAngle, endAngle: currentEndAngle } = getAngle(shapeModel, coordinate);
const { startAngle: preStartAngle, endAngle: preEndAngle } = getAngle(shape.get('origin'), coordinate);
const center = coordinate.getCenter();
// @ts-ignore FIXME: coordinate 支持下
const radius = coordinate.getRadius();
const diffStartAngle = currentStartAngle - preStartAngle;
const diffEndAngle = currentEndAngle - preEndAngle;
shape.animate((ratio) => {
const onFrameStartAngle = preStartAngle + ratio * diffStartAngle;
const onFrameEndAngle = preEndAngle + ratio * diffEndAngle;
return {
...toAttrs,
path: getSectorPath(center.x, center.y, radius, onFrameStartAngle, onFrameEndAngle),
};
}, animateCfg);
}
115 changes: 115 additions & 0 deletions src/geometry/animate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as _ from '@antv/util';
import { AnimateCfg, Data, Datum, ShapeDrawCFG } from 'interface';
import { Coordinate, IGroup, IShape } from '../../dependents';
import * as Action from './action';

// 默认动画配置
const DEFAULT_ANIMATE_CFG = {
interval: {
enter(coordinate: Coordinate) {
let animation;
if (coordinate.isRect) {
animation = coordinate.isTransposed ? 'scaleInX' : 'scaleInY';
} else {
if (coordinate.isTransposed) {
// pie chart
animation = 'pieChartEnter';
}
}
return {
animation,
duration: 450,
easing: 'easeQuadOut',
};
},
update(coordinate: Coordinate) {
if (coordinate.type === 'theta') {
return {
animation: 'pieChartUpdate',
duration: 450,
easing: 'easeQuadInOut',
};
}

return {
duration: 450,
easing: 'easeQuadInOut',
};
},
leave: {
animation: 'fadeOut',
duration: 400,
easing: 'easeQuadIn',
callback: (shape: IShape | IGroup) => {
shape.remove(true);
},
},
},
// line: {
// enter: {
// animation: 'clipIn',
// duration: 450,
// easing: 'easeQuadOut',
// },
// update: {
// duration: 450,
// easing: 'easeQuadInOut',
// },
// leave: {
// animation: 'fadeOut',
// duration: 350,
// easing: 'easeQuadIn',
// callback: (shape: IShape | IGroup) => {
// shape.remove(true);
// },
// },
// },
};

function getAnimateConfig(animateCfg: AnimateCfg, data: Data | Datum) {
return {
delay: _.isFunction(animateCfg.delay) ? animateCfg.delay(data) : animateCfg.delay,
easing: _.isFunction(animateCfg.easing) ? animateCfg.easing(data) : animateCfg.easing,
duration: _.isFunction(animateCfg.duration) ? animateCfg.duration(data) : animateCfg.duration,
callback: animateCfg.callback,
};
}

/**
* 获取默认的动画配置
* @param geometryType 几何标记类型
* @param animateType 动画类型
* @param coordinate 坐标系
* @returns default animate cfg
*/
export function getDefaultAnimateCfg(geometryType: string, animateType: string, coordinate: Coordinate): AnimateCfg {
const animateCfg = _.get(DEFAULT_ANIMATE_CFG, [geometryType, animateType], {});

if (_.isFunction(animateCfg)) {
return animateCfg(coordinate);
}

return animateCfg;
}

/**
* 工具函数根据用户传入的配置为 shape 执行动画
* @param shape 执行动画的图形元素
* @param cfg 图形配置
* @param [toAttrs] shape 最终状态的图形属性
*/
export function doAnimate(shape: IGroup | IShape, cfg: ShapeDrawCFG, coordinate: Coordinate, toAttrs?: object) {
const { animate, data, origin } = cfg;
const animation = animate.animation;
const animateCfg = getAnimateConfig(animate, data);
if (animation) {
// 用户声明了动画执行函数
const animateFunction = Action[animation];
if (animateFunction) {
animateFunction(shape, animateCfg, coordinate, origin, toAttrs);
}
} else {
// 没有声明,则根据 toAttrs 做差值动画
shape.animate(toAttrs, animateCfg);
}
}
Loading